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,477 @@
1
+ # Template contract
2
+
3
+ What the `langgraph` template guarantees, as implemented in `app/`. Agent code relies on these;
4
+ scaffolding files implement them.
5
+
6
+ ## File layout (kubernetes target, runtime fastapi, cd argocd)
7
+
8
+ ```
9
+ <name>/
10
+ ├── app/
11
+ │ ├── agent.py # exports `graph` (unbound compiled StateGraph)
12
+ │ ├── fast_api_app.py # exports `app`: /chat SSE, A2A, /health, /ready, /metrics, /threads, /playground (dev only), policy middleware, checkpointer binding
13
+ │ ├── app_utils/
14
+ │ │ ├── model.py # get_model(), get_judge_model() via init_chat_model (timeout, retries); FakeChatModel (provider `fake`)
15
+ │ │ ├── chat.py # the single invocation path shared by /chat, A2A and the playground: run lock (409), history repair, timeouts, step limit, error events, run records, retention
16
+ │ │ ├── checkpointer.py # memory | postgres from CHECKPOINTER (fastapi only); one health-checked pool per process, connect/keepalive defaults, lease-fenced saver
17
+ │ │ ├── run_locks.py # one run per thread: in-process locks plus Postgres leases (owner, fencing token, expiry) renewed by a heartbeat
18
+ │ │ ├── auth.py # Principal (hashed_id(), public_attributes()), AuthPolicy, SharedBearerPolicy, JwtPolicy, check_startup(), require(), `auth` for langgraph.json
19
+ │ │ ├── threads.py # thread ownership table (fastapi) / thread metadata (langgraph-server)
20
+ │ │ ├── api_client.py # get_client(), ApiClient, ApiPolicy, ApiPolicyError, ApiCallError; enforces api-policy.yaml
21
+ │ │ ├── limits.py # RUN_TIMEOUT_S, MODEL_*, RECURSION_LIMIT, request and metadata caps, SSE heartbeat, RETENTION_DAYS (a bad value stops startup)
22
+ │ │ ├── metrics.py # Prometheus registry for /metrics (METRICS_TOKEN)
23
+ │ │ ├── middleware.py # request id and JSON logging, body size cap, CORS, auth-error and thread-delete hooks (langgraph-server)
24
+ │ │ ├── telemetry.py # opt-in tracing, capture policy
25
+ │ │ ├── db.py # run records (`runs` table under postgres; `agent_runs` under langgraph-server; `running` until they end, reconciled to `interrupted`), lease table, schema setup under an advisory lock
26
+ │ │ ├── content.py # message content helpers
27
+ │ │ ├── structured.py # structured final answers: response_format(), the StructuredAnswer check, the JSON Schema subset
28
+ │ │ ├── playground.py # the /playground page (APP_ENV=dev only)
29
+ │ │ └── a2a.py # agent card (A2A 1.0 interface only) and JSON-RPC executor bridging the SSE events; tasks per principal in Postgres (a2a_tasks) or memory, A2A_TASK_TTL_S
30
+ │ ├── response_schema.json # only when the project declares one (create --response-schema): the JSON shape of the final answer
31
+ │ ├── policies/
32
+ │ │ └── custom.py # CustomPolicy stub (fails closed with HTTPException 503)
33
+ │ └── tools/
34
+ │ ├── __init__.py # collects TOOLS from every module; warns on a module without API_CALLS
35
+ │ ├── weather.py # get_weather (API_CALLS = []): an example, yours to replace or delete
36
+ │ └── example_api.py # call_<api>_api: the first operation the policy's first API allows,
37
+ │ # any method (a JSON `body` for POST/PUT/PATCH); only with a policy
38
+ ├── tests/
39
+ │ ├── conftest.py # isolation: no .env, no app settings from the shell; `use_test_tools` fixture
40
+ │ ├── unit/test_fake_model.py # the fake model calls whichever bound tool a request mentions
41
+ │ ├── unit/test_policy.py # auth policies (shared-bearer, custom stub, startup checks, aliases)
42
+ │ ├── unit/test_jwt_policy.py # jwt with locally generated keys: JWKS caching and rotation, claims, algorithms, 401/503
43
+ │ ├── unit/test_server_auth.py # langgraph-server handlers: owners, read-across, AUTH_ADMIN_ROLES, default deny
44
+ │ ├── unit/test_a2a_scoping.py # A2A tasks private per principal, TTL eviction
45
+ │ ├── unit/test_api_client.py # the API client: fail closed, rules, auth modes, path templates, traversal, paging, every method against a local server, limits; every tool's API_CALLS
46
+ │ ├── unit/test_limits.py # the limit settings and their parsing
47
+ │ ├── unit/test_logging.py # JSON logs, request ids, hashed principals
48
+ │ ├── unit/test_threads.py # ownership: owner / read-across role / stranger; tool-args redaction
49
+ │ ├── unit/test_telemetry.py # no error message or stack trace leaves under metadata capture
50
+ │ ├── integration/test_server_e2e.py # fastapi runtime in process (tool events with a test tool, error event, 503 stub, ownership)
51
+ │ ├── integration/test_runtime_guardrails.py # 409, timeouts, recursion limit, body and metadata caps, /ready, /metrics
52
+ │ ├── integration/test_server_runtime.py # langgraph-server branch against a fake SDK client
53
+ │ ├── integration/test_postgres.py # opt-in: TEST_POSTGRES_DSN (a server where the user may create databases)
54
+ │ ├── integration/test_model_apis.py # MODEL_REASONING_EFFORT / MODEL_USE_RESPONSES_API against a fake OpenAI server (fake_openai.py): both APIs, with a tool
55
+ │ ├── integration/test_structured_answers.py # a response schema: /chat and A2A deliver the answer (both strategies, approvals, memory and Postgres)
56
+ │ ├── integration/test_chart.py # kubernetes target: helm template/lint of every environment, expectations read from the values files (needs helm)
57
+ │ ├── eval/datasets/basic-dataset.json, eval/eval_config.yaml # judges: {} (built-in rubrics); cases pass on the fake model
58
+ │ └── load_test/ # excluded from a plain `pytest`
59
+ ├── deployment/helm/<name>/ # Chart.yaml, values.yaml, values-{dev,staging,prod}.yaml, templates/, charts/
60
+ ├── deployment/argocd/ # application-{dev,staging,prod}.yaml (cd = argocd only)
61
+ ├── .github/workflows/{pr_checks,staging,promote-to-prod}.yaml # staging/promote only when cd != skip
62
+ ├── .github/agent.env # GRAPH_AGENTS_CLI_SPEC (where CI installs the CLI) + chart settings; data, never sourced
63
+ ├── .github/CODEOWNERS # deployment/ (not the dev/staging values), .github/, api-policy.yaml, tests/eval/, extensions, manifest
64
+ ├── langgraph.json # always generated: graphs, http.app, auth
65
+ ├── Dockerfile # runtime-specific; runs as 1000:1000, read-only root filesystem compatible
66
+ ├── .dockerignore # keeps .env, .venv, .git, artifacts, tests, deployment out of the image
67
+ ├── .env.example # full env contract
68
+ ├── api-policy.yaml # with --api-policy, or once `graph-agents-cli api add` declares an API
69
+ ├── graph-agents-cli-manifest.yaml
70
+ ├── AGENTS.md | CLAUDE.md | GEMINI.md # may declare `process:` (default AGENTS.md)
71
+ └── pyproject.toml, uv.lock
72
+ ```
73
+
74
+ Under `langgraph-server` the same routes are provided by `app/fast_api_app.py` mounted through
75
+ `langgraph.json` `"http": {"app": "./app/fast_api_app.py:app"}` beside the native
76
+ Assistants/Threads/Runs API, and `"auth": {"path": "./app/app_utils/auth.py:auth"}` registers the
77
+ policy as the server's authentication handler. The chart sets `LANGGRAPH_SERVER=1` so the app
78
+ detects the mounted runtime. `app/fast_api_app.py` must not use
79
+ `from __future__ import annotations`: LangGraph Server executes the file as `user_router_module`
80
+ without registering it in `sys.modules`, and pydantic cannot resolve string annotations for such a
81
+ module (the `ChatBody` schema is then "not fully defined" and the server's OpenAPI generation
82
+ fails at startup). There is no `tools/policy_check.py` in the project; `lint` uses the CLI's own
83
+ checker (below).
84
+
85
+ ## Environment contract
86
+
87
+ Rendered into `.env.example` and the chart's `values.yaml` `env:` map.
88
+
89
+ | Variable | Set by | Meaning |
90
+ |---|---|---|
91
+ | `APP_ENV` | chart / `.env` | exactly `dev` enables `/playground`, `/docs`, the error `detail` and an optional jwt issuer and audience; anything else (`DEV`, ` dev`, unset) is a deployed environment |
92
+ | `MODEL_PROVIDER`, `MODEL_NAME` | `.env` / chart | agent model via `init_chat_model` |
93
+ | `OPENAI_BASE_URL` | `.env` / chart | only for `openai-compatible` |
94
+ | `OPENAI_API_KEY` \| `ANTHROPIC_API_KEY` \| `GOOGLE_API_KEY` \| `MODEL_API_KEY` | Secret | provider key |
95
+ | `JUDGE_MODEL_PROVIDER`, `JUDGE_MODEL_NAME`, `JUDGE_BASE_URL` | `.env` | judge; default to the agent's values |
96
+ | `JUDGE_API_KEY` | Secret | judge key; defaults to the provider key |
97
+ | `CHECKPOINTER` (`memory`\|`postgres`) | `.env`=memory, chart=postgres | fastapi only |
98
+ | `POSTGRES_DSN` | Secret (external) or chart env (subchart) | fastapi only |
99
+ | `DATABASE_URI`, `REDIS_URI` | Secret (external) or chart env (subchart) | langgraph-server only |
100
+ | `AUTH_POLICY` (`shared-bearer`\|`jwt`\|`custom`) | `.env` / chart | an unknown value never starts; `product-session` is read as `custom` (deprecated) |
101
+ | `API_KEY` | Secret | shared-bearer key |
102
+ | `AUTH_JWT_JWKS_URL` \| `AUTH_JWT_PUBLIC_KEY`, `AUTH_JWT_ISSUER`, `AUTH_JWT_AUDIENCE` | `.env` / chart | jwt only; issuer and audience required outside dev |
103
+ | `AUTH_JWT_ALGORITHMS` (`RS256,ES256`), `AUTH_JWT_PRINCIPAL_CLAIM` (`sub`), `AUTH_JWT_ROLES_CLAIM` (`roles`), `AUTH_JWT_LEEWAY_S` (60), `AUTH_JWT_JWKS_CACHE_S` (300), `AUTH_JWT_JWKS_ALLOW_HTTP` (false), `AUTH_JWT_ALLOW_HS` (false) | `.env` / chart | jwt only |
104
+ | `AUTH_JWT_SECRET` | Secret | jwt with HS* only (at least 32 bytes) |
105
+ | `AUTH_READ_ACROSS_ROLES` | chart | comma-separated roles allowed to read (never write) others' threads; empty default |
106
+ | `AUTH_ADMIN_ROLES` | chart | langgraph-server: roles allowed to manage assistants, crons and the store; empty = nobody |
107
+ | `AUTH_FORWARD_HEADERS` | `.env` / chart | langgraph-server with `LANGGRAPH_SERVER_URL`: request headers passed to the server's auth handler (default `authorization,cookie`) |
108
+ | `RUN_TIMEOUT_S` (300), `MODEL_TIMEOUT_S` (60), `MODEL_MAX_RETRIES` (2), `RECURSION_LIMIT` (50) | `.env` / chart | run guardrails |
109
+ | `MODEL_REASONING_EFFORT`, `MODEL_USE_RESPONSES_API` (unset) | `.env` / chart | OpenAI-API models: reasoning effort; `true` = the Responses API (see `langchain-models.md`) |
110
+ | `RESPONSE_FORMAT_STRATEGY` (`auto`) | `.env` / chart | with `app/response_schema.json`: `auto` (the provider's own structured output when the model has it and its client can send the schema, strict on OpenAI; else the `final_answer` tool; Anthropic's client refuses a type list and an `enum` with no `type`), `provider` or `tool` |
111
+ | `RESPONSE_SCHEMA_PATH` (`app/response_schema.json`) | `.env` | another response schema file; set, it must exist; `none` = text answers whatever the file (the project's tests set it; `tests/unit/test_structured.py` checks `app/response_schema.json` and that the agent answers in it) |
112
+ | `MAX_REQUEST_BYTES` (1048576), `MAX_METADATA_KEYS` (16), `MAX_METADATA_VALUE_CHARS` (256), `SSE_HEARTBEAT_S` (15) | `.env` / chart | request limits (413 / 422) and SSE keep-alive |
113
+ | `MAX_MESSAGE_CHARS` (32000) | `.env` / chart | longest user message on `/chat` (422) and A2A (invalid params, -32602) |
114
+ | `RETENTION_DAYS` (0) | `.env` / chart | purge threads idle longer than N days, hourly; 0 keeps everything |
115
+ | `LOG_LEVEL` (INFO), `LOG_FORMAT` (`json`, `text` under dev) | `.env` / chart | structured logs with request id, run id, thread id, hashed principal; access lines without query strings, outbound calls by API/method/operation/path template (`httpx` and `httpcore` at WARNING), warnings as records |
116
+ | `METRICS_ENABLED` (true) | `.env` / chart | `GET /metrics` |
117
+ | `METRICS_TOKEN`, `PRINCIPAL_HASH_SALT` | Secret (add to `secrets.keys`) | bearer token required by `/metrics`; HMAC key of the principal hash |
118
+ | `CORS_ALLOW_ORIGINS` | `.env` / chart | comma list; empty = no CORS |
119
+ | `DB_POOL_MIN_SIZE` (1), `DB_POOL_MAX_SIZE` (10) | `.env` / chart | connection pool per process |
120
+ | `A2A_TASK_TTL_S` (3600) | `.env` / chart | A2A tasks dropped this long after their last update (0 = kept until the thread is deleted, in memory until restart; a value that is not a whole number >= 0 stops startup) |
121
+ | `APP_URL` | chart (`appUrl` / hostname) / `.env` | public base URL in the A2A agent card; unset = bind address (warned outside dev) |
122
+ | `A2A_DESCRIPTION`, `AGENT_VERSION` (0.1.0) | `.env` / chart | the A2A card's description (and its chat skill's) and version; `A2A_NAME` (the agent directory) is its name and mount |
123
+ | `<API>_BASE_URL` (each API's `base_url_env`) | `.env` / chart | one per API in `api-policy.yaml` |
124
+ | each `auth: bearer` API's `token_env` | Secret | joins `secrets.keys` at create |
125
+ | `API_POLICY_PATH` | `.env` | default `./api-policy.yaml` |
126
+ | `TRACING_ENABLED` (`true`\|`false`) | `.env`=false | tracing opt-in |
127
+ | `TRACE_CAPTURE` (`metadata`\|`full`) | `.env`=metadata | capture policy; any other value stops startup |
128
+ | `LANGSMITH_API_KEY` | Secret | |
129
+ | `LANGSMITH_PROJECT`, `LANGSMITH_ENDPOINT` | `.env` / chart | project defaults to the project name |
130
+ | `OTEL_EXPORTER_OTLP_ENDPOINT` | chart | OTLP fallback |
131
+ | `PORT` | chart | default 8000 |
132
+
133
+ Client side (not in the app): `GRAPH_AGENTS_CLI_API_KEY`, the bearer credential `run` and `eval`
134
+ send locally and with `--url` (the `API_KEY`, or a JWT; `graph-agents-cli auth dev-token` mints a
135
+ local one for a `jwt` project).
136
+
137
+ ## Chat API (both runtimes)
138
+
139
+ `POST /chat` with header `Accept: text/event-stream`.
140
+
141
+ Request body:
142
+
143
+ ```json
144
+ { "thread_id": "optional-uuid", "message": "user text", "metadata": {} }
145
+ ```
146
+
147
+ Response: SSE. Each event is `event: <type>\ndata: <json>\n\n`.
148
+
149
+ | event | data |
150
+ |---|---|
151
+ | `message.start` | `{"thread_id": "...", "run_id": "..."}` |
152
+ | `message.delta` | `{"text": "..."}` |
153
+ | `tool.call` | `{"id": "...", "name": "...", "args": {...}}` (args omitted when `TRACE_CAPTURE=metadata` and the caller is not the owner; always present to the caller) |
154
+ | `tool.result` | `{"id": "...", "name": "...", "result": "...", "is_error": false}`; a failed call adds `"error_id"` and, outside `APP_ENV=dev`, its `result` is `"The tool call did not succeed. Reference: <error_id>."` (the error text is for the model only) |
155
+ | `message.end` | `{"thread_id": "...", "run_id": "...", "usage": {"input_tokens": n, "output_tokens": n}, "latency_ms": n, "status": "ok\|step_limit\|awaiting_approval"}`; with `awaiting_approval`, also `"approval": {"approval_id", "api", "method", "path", "query", "body", "operation_id", "reason", "approvers", "expires_at"}`; a completed run of a project with a response schema also `"structured_response": {...}` |
156
+ | `error` | `{"code": "run_failed\|timeout\|recursion_limit\|thread_busy\|unavailable\|forbidden\|invalid_structured_response", "message": "...", "error_id": "...", "run_id": "..."}` (plus `detail` only under `APP_ENV=dev`) then the stream closes |
157
+
158
+ `message.end` has `"status": "ok"`, or `"step_limit"` when the run reached `RECURSION_LIMIT` and
159
+ ended with a reply saying so (the reply is the preceding `message.delta`; the run's work stays in
160
+ the thread). An `error` event replaces `message.end` on failure (`recursion_limit` only when the
161
+ step-limit reply could not be written). `"awaiting_approval"` ends a run paused before a call its
162
+ API's `approval` block gates (the `approval` object names the call; body fields on the optional
163
+ redact list are masked); it is resumed by deciding the approval (below), not by a new message.
164
+ Other graph interrupts have no status and no resume convention. Idle streams get a `: keep-alive` comment every
165
+ `SSE_HEARTBEAT_S`. Run records carry `running` while the run is in progress, then `ok`,
166
+ `step_limit`, `error`, `timeout`, `cancelled` (a client disconnect) or `interrupted` (the run
167
+ lost its lease, or its process died; `/metrics` counts the same final statuses). A run stopped
168
+ mid tool call leaves a call without a result: the next run answers it with an error result
169
+ right after the call before adding its turn, so the thread stays valid. A tool call whose
170
+ arguments are not valid JSON runs no tool: it arrives as `tool.call` with `"args": {}` and an
171
+ error `tool.result`, and the model is asked again in the same step (`AnswerInvalidToolCalls`).
172
+
173
+ Request rules: a second `/chat` on a thread with a run in progress gets 409
174
+ `{"code": "thread_busy"}`, and one on a thread whose run awaits an approval 409
175
+ `{"code": "approval_pending"}`; a body over `MAX_REQUEST_BYTES` gets 413; a message over
176
+ `MAX_MESSAGE_CHARS`, text with an unpaired surrogate, metadata outside the caps (or
177
+ with non-scalar values), `NaN`/`Infinity`, or a `thread_id` outside 1-128 characters of
178
+ `[A-Za-z0-9_.:-]` (a non-UUID under langgraph-server) gets 422, whose `detail` never echoes the
179
+ submitted values (`input`); a missing `thread_id` starts a thread with a random
180
+ server-generated id (ids are one namespace: an id another principal used first is theirs, so
181
+ client-chosen ids must be unguessable); a database or server outage gets
182
+ 503 with a reference; an unhandled error gets 500 `{"detail": "Internal server error. Reference:
183
+ <id>.", "error_id": ...}`. Every response carries `X-Request-ID`. Client metadata is stored in the
184
+ run record, never in checkpoints, and exported to traces only under `TRACE_CAPTURE=full`.
185
+
186
+ Other routes:
187
+
188
+ - `GET /health` -> `{"status": "ok", "runtime": "fastapi|langgraph-server", "checkpointer": "memory|postgres"}` (liveness, no auth)
189
+ - `GET /ready` -> 200 `{"status": "ready"}` when the database (and run store) answer within 2 s, else 503 `{"status": "not_ready"}` (no auth)
190
+ - `GET /metrics` -> Prometheus text (no auth unless `METRICS_TOKEN`; 404 when `METRICS_ENABLED=false`)
191
+ - `GET /threads?limit=&offset=&scope=own|all` -> `[{thread_id, owner, created_at, updated_at}]`, most recent first (`thread.list`); `owner` is the hashed principal id; `scope=own` (default) is the caller's threads, a read-across role included; `scope=all` lists every principal's, for a role in `AUTH_READ_ACROSS_ROLES` only (403 otherwise)
192
+ - `GET /threads/{thread_id}/messages` -> ordered messages (ownership enforced); a failed tool call's message carries `error_id` and, outside dev, the same generic text as its `tool.result`
193
+ - `GET /threads/{thread_id}/approvals` -> the thread's approvals (the thread's owner, its approvers, read-across roles): the call, `reason`, `approvers`, `status` (`pending`, `approved`, `rejected`, `expired`), `expires_at`, decision time and comment
194
+ - `GET /approvals?status=&limit=&offset=` -> across threads, newest first: the caller's own approvals, the ones naming one of its roles, and every one for a read-across role (each row carries its `thread_id`)
195
+ - `POST /threads/{thread_id}/approvals/{approval_id}` with `{"decision": "approve"|"reject", "comment": "..."}` (`approval.decide`: `requester` is the principal who started the run, `role:<x>` any other principal with role x; a requester decides their own call only when `requester` is listed) -> the resumed run as SSE with the events above; 403 not an approver, 404 unknown, 409 not pending, 410 expired
196
+ - `DELETE /threads/{thread_id}` -> 204; the thread, its checkpoints, run records, approvals and A2A tasks (owner only; 409 while a run is in progress). Under langgraph-server it is the server's native route
197
+ - `GET /playground`, `/docs`, `/openapi.json` -> only when `APP_ENV=dev`
198
+ - A2A: card at `/a2a/<agent_directory>/.well-known/agent-card.json`, JSON-RPC at `/a2a/<agent_directory>`;
199
+ the card advertises only the A2A 1.0 JSON-RPC interface (0.3 clients are served on the same URL
200
+ via compat) and the security scheme of the active auth policy. A message with no text, an
201
+ empty text part, a non-user role or over `MAX_MESSAGE_CHARS` is a JSON-RPC invalid-params
202
+ error (-32602) on both protocol versions, and an unknown task is -32001 on both (no error
203
+ log). `SendMessage` returns the reply as one text part
204
+ of the `response` artifact; streamed, it arrives in chunks, the last with `lastChunk`, and the
205
+ stored task keeps it as one part. A gated run moves the task to `input-required` with the
206
+ approval in a data part; a message on the same task with the data part `{"approval_id": ...,
207
+ "decision": "approve"|"reject"}` resumes it (same approver rules)
208
+
209
+ Structured final answers: with `app/response_schema.json` (a JSON Schema, root an object) the
210
+ agent answers in that shape (`app_utils/structured.py`: LangChain's provider strategy, strict,
211
+ where the model has it, else a `final_answer` tool; `RESPONSE_FORMAT_STRATEGY`), every answer is
212
+ checked (3 tries, then the `error` code `invalid_structured_response`), and a completed run
213
+ sends the answer's JSON text as its only `message.delta` and the object as `message.end`'s
214
+ `structured_response`; the answer tool never shows as `tool.call`. The A2A `response`
215
+ artifact adds a data part with the object (`mediaType` `application/json`). A schema may use
216
+ `type`, `enum`, `const`, `properties`, `required`, `additionalProperties`, `minProperties`,
217
+ `maxProperties`, `items` (one schema), `minItems`, `maxItems`, `uniqueItems`, `minLength`,
218
+ `maxLength`, `pattern` (Python syntax), `minimum`, `maximum`, `exclusiveMinimum`,
219
+ `exclusiveMaximum`, `multipleOf`, `anyOf`, `oneOf`, `allOf`, `not`, `$ref` to its own `$defs`
220
+ or `definitions`, and annotations (`title`, `description`, `default`, `examples`, `format`,
221
+ ...); anything else is refused at startup and by `lint`. Under OpenAI's strict mode every
222
+ property is required: let one that may have no value be `null` (`anyOf` with `{"type":
223
+ "null"}`), and write a `type` beside every `enum` (Anthropic's client refuses an `enum` alone
224
+ or a type list, so `auto` falls back to the tool strategy).
225
+
226
+ `eval generate` derives `response`, `tool_calls`, `usage`, `latency_ms`, `status` and
227
+ `structured_response` from these events; the A2A executor bridges the same events to task
228
+ artifacts.
229
+
230
+ ## Auth policy (`app/app_utils/auth.py`)
231
+
232
+ ```python
233
+ @dataclass
234
+ class Principal:
235
+ id: str
236
+ roles: list[str] = field(default_factory=list)
237
+ permissions: set[str] = field(default_factory=set)
238
+ attributes: dict[str, Any] = field(default_factory=dict)
239
+
240
+ def hashed_id(
241
+ self,
242
+ ) -> str: ... # sha256 (HMAC with PRINCIPAL_HASH_SALT when set), first 16 hex chars
243
+ def public_attributes(self) -> dict: ... # attributes without "credentials"
244
+
245
+
246
+ class AuthPolicy(Protocol):
247
+ async def authenticate(self, request: Request) -> Principal: ... # raise HTTPException(401)
248
+ async def authorize(
249
+ self, principal: Principal, action: str, resource: str | None
250
+ ) -> None: ... # raise HTTPException(403)
251
+
252
+
253
+ ACTIONS = {
254
+ "chat.send",
255
+ "thread.read",
256
+ "thread.list",
257
+ "thread.delete",
258
+ "run.read",
259
+ "a2a.invoke",
260
+ "card.read",
261
+ }
262
+ ```
263
+
264
+ `get_policy()` returns the instance for `AUTH_POLICY` (the registry is `app/policies/__init__.py`).
265
+ `check_startup()` builds it when the app is assembled and when LangGraph Server loads `auth`: an
266
+ unknown policy never starts, and a policy whose optional `startup_problems() -> list[str]` reports
267
+ a problem stops the process outside `APP_ENV=dev` (under dev the problem is logged and requests
268
+ get 503). `SharedBearerPolicy` checks `Authorization: Bearer <API_KEY>` (constant-time compare)
269
+ and returns `Principal(id="shared")`; an unset `API_KEY` is 503. `JwtPolicy` (`jwt`) maps a
270
+ verified OIDC/JWT bearer token to a per-user principal (401 with an RFC 6750 challenge for a
271
+ missing or invalid token, 503 when misconfigured or the issuer's keys are unavailable). `CustomPolicy`
272
+ lives in `app/policies/custom.py` and fails closed with an `HTTPException(503)` carrying the
273
+ implementation instructions (`require()` also maps a `NotImplementedError` to 503) until
274
+ implemented. `Principal.attributes` may hold secrets only under `credentials` (api name ->
275
+ credential, forwarded by `auth: forward` APIs); `Principal.public_attributes()` drops them and is
276
+ what may be persisted, logged, traced or passed into LangGraph Server run context. Under
277
+ `fastapi`, thread ownership is enforced by the app-owned `threads` table check before any
278
+ checkpointer access. `auth` (a `langgraph_sdk.Auth`) is built from the same policy for
279
+ `langgraph.json`. Clients: a bearer credential in `GRAPH_AGENTS_CLI_API_KEY` (never on the command
280
+ line), `--header 'Name: value'` or `--cookie name=value` for what a custom policy reads.
281
+
282
+ ## API client (`app/app_utils/api_client.py`) and `api-policy.yaml`
283
+
284
+ ```yaml
285
+ # api-policy.yaml (owned by the project; example values, not defaults). Unknown keys are errors.
286
+ apis:
287
+ incidents: # [a-z][a-z0-9_]*, at most 32 characters
288
+ base_url_env: INCIDENTS_API_BASE_URL # required; the URL may carry a path prefix
289
+ auth: bearer # required: none | bearer | forward | exchange
290
+ token_env: INCIDENTS_API_TOKEN # required iff auth: bearer
291
+ # forward_header: Authorization # auth: forward or exchange only (the default)
292
+ # forward_audience: incidents # auth: forward only: forward the caller's own token
293
+ # exchange: {audience: incidents} # required iff auth: exchange (+ scope, resource,
294
+ # allow_actorless)
295
+ allowed_methods: [GET, POST] # required, explicit (no default); ["*"] = every method
296
+ allowed_operations: # optional; omit = every operation within allowed_methods
297
+ - operationId: getIncident
298
+ path: /incidents/{incident_id} # both pinned: both must match
299
+ - operationId: acknowledgeIncident
300
+ path: /incidents/{incident_id}/ack
301
+ methods: [POST]
302
+ - path: /sites/{siteId}/topology
303
+ methods: [GET]
304
+ denied_operations: # same entry shape; denials win and fail closed
305
+ - operationId: closeIncident
306
+ path: /incidents/{incident_id}/close
307
+ openapi: docs/incidents-openapi.yaml # optional; lint validates declared calls against it
308
+ timeouts_ms: {connect: 2000, read: 5000}
309
+ pagination: {page_size_param: pageSize, max_page_size: 200} # enforced at runtime
310
+ limits: {max_calls_per_run: 20, rate_per_minute: 120} # optional; per run / per replica
311
+ approval: # optional: calls a human approves before they are sent
312
+ required_for: # methods and/or operations (entry shape above)
313
+ methods: [POST]
314
+ operations:
315
+ - operationId: acknowledgeIncident
316
+ path: /incidents/{incident_id}/ack
317
+ approvers: [requester, "role:oncall-lead"] # requester and/or role:<name>
318
+ timeout_s: 900 # 30-86400; then the approval expires (rejected)
319
+ ```
320
+
321
+ `approval` may instead be a non-empty list of rules, each of the shape above; the first rule
322
+ in file order whose `required_for` covers a call gates it, with that rule's approvers:
323
+
324
+ ```yaml
325
+ approval:
326
+ - required_for: # pin path and methods: see below
327
+ operations:
328
+ - {operationId: acknowledgeIncident, path: "/incidents/{incident_id}/ack", methods: [POST]}
329
+ approvers: [requester]
330
+ - required_for: {methods: [POST]} # every other POST: a second person
331
+ approvers: ["role:oncall-lead"]
332
+ timeout_s: 3600
333
+ ```
334
+
335
+ An entry by `operationId` alone cannot rule out a call that names no operation id, so when a
336
+ later rule with other approvers also covers such a call it could be either rule's: `gated()`
337
+ raises `ApprovalRuleConflict` and the client refuses it (`ApiPolicyError`, nothing sent),
338
+ rather than letting the first rule's approvers decide a call meant for the later one. `lint`
339
+ reports such declared calls and `api approval` notes the rule. Pin `path` and `methods` in
340
+ entries of a rule that comes before a broader one (`api approval --operations` pins them from
341
+ the API's `openapi:` spec), and name `operation_id` on every call.
342
+
343
+ - `get_client(name)` returns a policy-enforcing async client for one declared API. It fails
344
+ closed: no file, an invalid file or an undeclared API raise `ApiPolicyError`; there is no
345
+ unrestricted fallback. `request(method, path, operation_id=None, path_params=None, params=None,
346
+ json_body=None, headers=None)` (and `get`, `head`, `post`, `put`, `patch`, `delete`,
347
+ `options`) sends any allowed method with a JSON body, query parameters and headers, and
348
+ refuses, before sending, any method or operation outside the policy (`ApiPolicyError`, a tool
349
+ error the model can read); configuration or HTTP failures raise `ApiCallError` (a non-2xx
350
+ response sets `status_code` and `body`, the start of the error body with the sent credential
351
+ redacted). An empty response body returns `""`. Tool-supplied `Host`, method-override,
352
+ `X-Forwarded-*`, `Forwarded`, `X-Original-URL`, `X-Rewrite-URL` and hop-by-hop headers are
353
+ dropped, and a `_method` query parameter or top-level JSON body key is refused. Each call is
354
+ logged by API, method, operation id and path template, never its values.
355
+ - The caller, for write tools: `current_caller(context)` (fails closed without a principal),
356
+ `require_owner(owner_id, context=..., allow_roles=())` and `require_user_mentioned(value,
357
+ runtime)` raise `ApiPolicyError` (a tool error) for a record that is not the caller's or an id
358
+ the user's latest message does not name.
359
+ - `limits` (optional, per API): `max_calls_per_run` counts the calls to that API in one agent
360
+ run (the run id from the LangGraph run's config metadata, else the request's; `get_client(...,
361
+ run_id=...)` names it explicitly; calls outside any run share one count) and
362
+ `rate_per_minute` is a token bucket per process, so per replica. Both are checked just before
363
+ sending and raise `ApiPolicyError`. A run's counters are dropped when a `/chat` or A2A run
364
+ ends (`end_run`), and otherwise (LangGraph Server runs included) after an hour without a
365
+ call; at most 10 000 runs are tracked.
366
+ - `approval` (per API, one rule or a list of rules): a call `required_for` covers (its method,
367
+ or an `operations` entry that holds like a denial: the entry's path whatever label the call
368
+ gives, its operationId, or a call leaving out what the entry knows it by) pauses the run in
369
+ the client before sending (LangGraph `interrupt()` with the approval; the canonical request
370
+ is hashed). With a list, the first rule in file order that covers the call (the path sent,
371
+ or the template it was rendered from) gates it: its approvers are the ones the approval is
372
+ asked of, recorded with it and deciding it; later rules that also cover the call do not
373
+ apply to it (`gated()` returns the rule's `index`, and `also` for the later ones). A call the
374
+ first rule covers only because it names no operation id, and that a later rule with other
375
+ approvers also covers, is refused (`ApprovalRuleConflict`). Approved: the client re-hashes
376
+ the request it is about to send, refuses
377
+ it (nothing sent) when it differs, and sends it once. Rejected or expired: nothing is sent and
378
+ the tool gets a "not approved" error. A decision is bound to its call (API, method, path) on
379
+ resume, whatever the policy says about gating it by then: a rejected or expired call is never
380
+ sent, an approved one only while the policy still allows it and gates it with the same
381
+ approvers, and a call still pending when another decision resumes the run pauses again for
382
+ its own approval (a call the policy now refuses is refused, and its approval expires when
383
+ the run ends). The ledger binds a decision to its tool call as well (the model message and
384
+ call id, else the task's interrupt): a tool call that runs again with no decision waiting
385
+ (LangGraph Server's own API continuing a paused run without input or replaying it from a
386
+ checkpoint, a copied thread) has a call an approval was asked for refused, whatever the
387
+ policy now says; an approved one is sent only by the run its decision resumed, once. Under
388
+ `langgraph-server` the auth handler also refuses (403) a native run without input or from a
389
+ checkpoint on a thread that has approvals or waits on a gated call, and a copy of a thread
390
+ that has approvals. Only calls the policy allows are gated (approval never widens access). An `approval` key on an operation entry is refused ("not valid on an
391
+ operation entry; gate the operation with apis.<name>.approval.required_for.operations").
392
+ The tool re-runs from its start on resume: keep it idempotent up to the call and the request
393
+ deterministic. Approvals are stored in an `approvals` table beside the checkpoints (under
394
+ the local `langgraph dev`, in `.langgraph_api/agent_approvals.json` beside its threads, so
395
+ both survive a restart or a hot reload).
396
+ - Matching: an allowed entry pinning several fields needs all of them to match. Denials win
397
+ and hold on the endpoint: a denial covers every call to a path its `path` covers (with its
398
+ `methods`), whatever `operation_id` the call names, and every call naming its
399
+ `operationId`; failing closed, it also covers a call that leaves out what it knows the
400
+ operation by (no `operation_id` against a denial by `operationId` alone, no path in
401
+ `API_CALLS` against a denial pinning a path). With `openapi:`, `lint` refuses a declared
402
+ `operation_id` the spec does not give that method and path. Paths are compared after
403
+ decoding percent-encoded unreserved characters and ignoring one trailing slash; allows are
404
+ case-sensitive, denials and gates are not, and a denial or gate also covers a literal
405
+ segment's dot-suffixed spellings (`cancel.json`, `cancel.`), which servers that route format
406
+ suffixes or drop a trailing dot send to the same endpoint; allows never match that way. A
407
+ segment with a control character or whitespace at either end or next to a dot, also
408
+ percent-encoded (`cancel%20`, `cancel%20.json`, `7%00`), is refused in declared paths
409
+ (`lint`) and in the paths sent; so are an encoded slash, backslash, `;` or dot segment
410
+ (`%2F`, `%5C`, `%3B`, `%2e%2e`). `pagination.max_page_size` applies to every value of the
411
+ parameter, in any letter case. Repeated YAML keys are errors, like unknown keys.
412
+ - `auth: bearer` sends `Authorization: Bearer $<token_env>`; `auth: forward` sends the calling
413
+ principal's `attributes["credentials"][<api>]` in `forward_header` (the principal comes from
414
+ the run context, or `get_client(..., context=runtime.context)`) and sends nothing when the
415
+ caller has none; `auth: exchange` sends `Bearer <token>`, a token the issuer mints for
416
+ `exchange.audience` in exchange for the caller's own (RFC 8693, `TOKEN_EXCHANGE_*` settings),
417
+ asked for just before sending, and refused (nothing sent) when it names no actor (no `act`
418
+ claim, or not a readable JWT) unless `exchange.allow_actorless: true`, which needs the called
419
+ agent to set `AUTH_JWT_DIRECT_CLIENTS`; `forward` and `exchange` are refused at create and lint under
420
+ `langgraph-server`, and lint checks them against the auth policy.
421
+ - Every `*.py` under `app/tools/` (subpackages included, the top-level `__init__.py` excluded) declares one module-level **literal**
422
+ `API_CALLS = [{"api": ..., "method": ..., "operation_id": ..., "path": ...}]` (`[]` when it
423
+ calls no external API) and `TOOLS`. `graph-agents-cli lint` runs the CLI's
424
+ `dev/policy_check.py`, which reads those literals with `ast` (no import, no model SDK),
425
+ validates `api-policy.yaml` with the runtime's own schema rules (the two copies are kept
426
+ byte-identical by a CLI test), and checks every call against its API and, when `openapi:` is
427
+ set (resolved relative to the project root), the spec by `operationId` or `path` + `method`
428
+ (a call declared by `operation_id` alone is judged with the spec's path for it).
429
+ `pr_checks` fails on a violation.
430
+ - Both Dockerfiles copy `api-policy.yaml` into the image when the project has one. The manifest
431
+ records `api_policy: {policy_file: api-policy.yaml}`; the key is absent without a policy. Any
432
+ other `policy_file` is a config error (exit 3): the agent loads only `api-policy.yaml`.
433
+ - The policy belongs to the project and evolves with it: `create --api-policy` only seeds it,
434
+ `scaffold enhance` and `upgrade` leave it untouched, and `graph-agents-cli api` (`add`,
435
+ `access`, `allow`, `deny`, `revoke`, `limits`, `remove`, `show`, `check`) changes it with a
436
+ diff, keeping comments, the manifest (`api_policy`, `secrets.keys`), `.env.example` and the
437
+ chart values in step. There is no default access level: `api add --access
438
+ read-only|read-write|custom` is required and writes the methods explicitly. Widening access is
439
+ a reviewed change (CODEOWNERS covers `api-policy.yaml`). A project on the retired
440
+ `product-policy.yaml` / `product_api:` format stops `create`, `enhance`, `upgrade` and `lint`
441
+ with migration steps (exit 3).
442
+
443
+ ## Manifest (`graph-agents-cli-manifest.yaml`)
444
+
445
+ ```yaml
446
+ name: my-agent
447
+ cli_version: 0.3.1
448
+ cli_build: # the build that rendered the project (scaffold upgrade reads it)
449
+ id: 0.3.1+g1a2b3c4 # `graph-agents-cli --version`; 0.3.1 for the release
450
+ commit: 1a2b3c4d... # full commit; null when not built from git
451
+ template_digest: sha256:... # what that build renders for these settings; null after a seed policy
452
+ agent_directory: app
453
+ base_template: langgraph
454
+ generated_at: 2026-09-22T00:00:00+00:00
455
+ language: python
456
+ create_params:
457
+ deployment_target: kubernetes # kubernetes | none
458
+ runtime: fastapi # fastapi | langgraph-server
459
+ model_provider: openai # openai | anthropic | gemini | openai-compatible
460
+ model: gpt-5-mini
461
+ checkpointer: postgres # memory | postgres (deployed default)
462
+ registry: ghcr.io/my-org
463
+ cd: skip # argocd | helm-push | skip
464
+ auth_policy: shared-bearer # shared-bearer | jwt | custom
465
+ auth_policy_implemented: true # false while the custom stub is in place
466
+ agent_guidance_filename: AGENTS.md
467
+ environments:
468
+ dev: { context: "", namespace: my-agent-dev }
469
+ staging: { context: "", namespace: my-agent-staging }
470
+ prod: { context: "", namespace: my-agent-prod }
471
+ secrets:
472
+ keys: [OPENAI_API_KEY, JUDGE_API_KEY, POSTGRES_DSN, API_KEY, LANGSMITH_API_KEY]
473
+ owner: "platform-team"
474
+ api_policy:
475
+ policy_file: api-policy.yaml # absent when no policy is declared
476
+ process: null # or a path to a governing process document
477
+ ```