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.
- graph_agents_cli/__init__.py +26 -0
- graph_agents_cli/_api_policy.py +2145 -0
- graph_agents_cli/_approvals.py +400 -0
- graph_agents_cli/_build.py +186 -0
- graph_agents_cli/_build_info.json +7 -0
- graph_agents_cli/_chat_client.py +462 -0
- graph_agents_cli/_click.py +157 -0
- graph_agents_cli/_defaults.py +139 -0
- graph_agents_cli/_experiments.py +64 -0
- graph_agents_cli/_http.py +192 -0
- graph_agents_cli/_output.py +83 -0
- graph_agents_cli/_project.py +462 -0
- graph_agents_cli/_remote.py +220 -0
- graph_agents_cli/_response_schema.py +264 -0
- graph_agents_cli/_runner.py +319 -0
- graph_agents_cli/_skills_check.py +274 -0
- graph_agents_cli/_tools.py +189 -0
- graph_agents_cli/_trust.py +66 -0
- graph_agents_cli/api/__init__.py +15 -0
- graph_agents_cli/api/_changes.py +506 -0
- graph_agents_cli/api/_files.py +658 -0
- graph_agents_cli/api/cmd_api.py +2480 -0
- graph_agents_cli/deploy/__init__.py +15 -0
- graph_agents_cli/deploy/_config.py +171 -0
- graph_agents_cli/deploy/_image.py +128 -0
- graph_agents_cli/deploy/_kube.py +286 -0
- graph_agents_cli/deploy/_modes.py +234 -0
- graph_agents_cli/deploy/_preflight.py +370 -0
- graph_agents_cli/deploy/_values.py +168 -0
- graph_agents_cli/deploy/cmd_deploy.py +1866 -0
- graph_agents_cli/deploy/gitops.py +562 -0
- graph_agents_cli/deploy/local_load.py +273 -0
- graph_agents_cli/dev/__init__.py +13 -0
- graph_agents_cli/dev/cmd_build.py +131 -0
- graph_agents_cli/dev/cmd_install.py +78 -0
- graph_agents_cli/dev/cmd_lint.py +119 -0
- graph_agents_cli/dev/cmd_playground.py +297 -0
- graph_agents_cli/dev/policy_check.py +1287 -0
- graph_agents_cli/eval/__init__.py +22 -0
- graph_agents_cli/eval/_client.py +670 -0
- graph_agents_cli/eval/_common.py +177 -0
- graph_agents_cli/eval/_judge.py +168 -0
- graph_agents_cli/eval/_judge_runner.py +238 -0
- graph_agents_cli/eval/_paths.py +212 -0
- graph_agents_cli/eval/checks.py +581 -0
- graph_agents_cli/eval/cmd_analyze.py +278 -0
- graph_agents_cli/eval/cmd_compare.py +284 -0
- graph_agents_cli/eval/cmd_eval_group.py +80 -0
- graph_agents_cli/eval/cmd_generate.py +558 -0
- graph_agents_cli/eval/cmd_grade.py +466 -0
- graph_agents_cli/eval/cmd_metric.py +156 -0
- graph_agents_cli/eval/cmd_run.py +370 -0
- graph_agents_cli/eval/cmd_submit.py +400 -0
- graph_agents_cli/eval/config.py +435 -0
- graph_agents_cli/eval/dataset.py +350 -0
- graph_agents_cli/eval/gate.py +420 -0
- graph_agents_cli/eval/transcript.py +192 -0
- graph_agents_cli/extension/__init__.py +13 -0
- graph_agents_cli/extension/_compat.py +86 -0
- graph_agents_cli/extension/_loader.py +293 -0
- graph_agents_cli/extension/_manifest.py +135 -0
- graph_agents_cli/extension/_overrides.py +195 -0
- graph_agents_cli/extension/_paths.py +91 -0
- graph_agents_cli/extension/_refs.py +193 -0
- graph_agents_cli/extension/_resolver.py +453 -0
- graph_agents_cli/extension/_schema.py +106 -0
- graph_agents_cli/extension/_spec.py +253 -0
- graph_agents_cli/extension/_sync.py +102 -0
- graph_agents_cli/extension/_trust.py +58 -0
- graph_agents_cli/extension/cmd_extension_add.py +259 -0
- graph_agents_cli/extension/cmd_extension_group.py +57 -0
- graph_agents_cli/extension/cmd_extension_list.py +56 -0
- graph_agents_cli/extension/cmd_extension_remove.py +61 -0
- graph_agents_cli/extension/cmd_extension_update.py +195 -0
- graph_agents_cli/info/__init__.py +13 -0
- graph_agents_cli/info/cmd_info.py +222 -0
- graph_agents_cli/infra/__init__.py +15 -0
- graph_agents_cli/infra/checks.py +1169 -0
- graph_agents_cli/infra/cmd_infra.py +103 -0
- graph_agents_cli/main.py +591 -0
- graph_agents_cli/peer/__init__.py +15 -0
- graph_agents_cli/peer/_generate.py +254 -0
- graph_agents_cli/peer/cmd_peer.py +1151 -0
- graph_agents_cli/run/__init__.py +13 -0
- graph_agents_cli/run/_local_server.py +1157 -0
- graph_agents_cli/run/_signals.py +141 -0
- graph_agents_cli/run/cmd_approvals.py +530 -0
- graph_agents_cli/run/cmd_run.py +1421 -0
- graph_agents_cli/scaffold/__init__.py +19 -0
- graph_agents_cli/scaffold/agents/README.md +24 -0
- graph_agents_cli/scaffold/agents/empty_py/.template/templateconfig.yaml +22 -0
- graph_agents_cli/scaffold/agents/langgraph/.env.example +292 -0
- graph_agents_cli/scaffold/agents/langgraph/.template/templateconfig.yaml +28 -0
- graph_agents_cli/scaffold/agents/langgraph/Dockerfile +59 -0
- graph_agents_cli/scaffold/agents/langgraph/Dockerfile.langgraph-server +59 -0
- graph_agents_cli/scaffold/agents/langgraph/README.md +571 -0
- graph_agents_cli/scaffold/agents/langgraph/api-policy.yaml +60 -0
- graph_agents_cli/scaffold/agents/langgraph/app/__init__.py +20 -0
- graph_agents_cli/scaffold/agents/langgraph/app/agent.py +174 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/__init__.py +15 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/a2a.py +2162 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/a2a_client.py +1167 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/api_client.py +4220 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/approvals.py +1349 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/auth.py +1986 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/chat.py +2962 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/checkpointer.py +432 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/content.py +569 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/db.py +580 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/limits.py +203 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/metrics.py +231 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/middleware.py +361 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/model.py +611 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/playground.py +230 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/run_locks.py +459 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/structured.py +755 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/telemetry.py +681 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/threads.py +493 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/token_exchange.py +959 -0
- graph_agents_cli/scaffold/agents/langgraph/app/fast_api_app.py +770 -0
- graph_agents_cli/scaffold/agents/langgraph/app/policies/__init__.py +55 -0
- graph_agents_cli/scaffold/agents/langgraph/app/policies/custom.py +97 -0
- graph_agents_cli/scaffold/agents/langgraph/app/tools/__init__.py +46 -0
- graph_agents_cli/scaffold/agents/langgraph/app/tools/example_api.py +92 -0
- graph_agents_cli/scaffold/agents/langgraph/app/tools/weather.py +33 -0
- graph_agents_cli/scaffold/agents/langgraph/langgraph.json +14 -0
- graph_agents_cli/scaffold/agents/langgraph/pyproject.toml +78 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/conftest.py +376 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/eval/datasets/basic-dataset.json +53 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/eval/eval_config.yaml +32 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/approval_graph.py +137 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/fake_issuer.py +216 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/fake_openai.py +357 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_a2a_outcomes.py +569 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_a2a_relay.py +479 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_api_surface.py +812 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_approvals.py +1367 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_approvals_server.py +794 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_cross_actor.py +497 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_cross_actor_server.py +247 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_history_repair.py +278 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_model_apis.py +242 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_postgres.py +637 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_resilience_postgres.py +770 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_runtime_guardrails.py +854 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_server_e2e.py +340 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_server_runtime.py +989 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_structured_answers.py +584 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_structured_server.py +222 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_token_exchange_issuer.py +650 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/load_test/.results/.placeholder +0 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/load_test/README.md +22 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/load_test/conftest.py +21 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/load_test/load_test.py +81 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_a2a_client.py +824 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_a2a_scoping.py +724 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_api_client.py +1214 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_api_client_hardening.py +716 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_api_policy_rpc.py +767 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_approval_ledger.py +1536 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_fake_model.py +115 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_jwt_policy.py +991 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_limits.py +310 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_logging.py +148 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_logging_hardening.py +271 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_policy.py +378 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_resilience.py +610 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_server_auth.py +702 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_structured.py +673 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_telemetry.py +404 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_thread_listing.py +255 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_threads.py +268 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_token_exchange.py +1320 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_untrusted_content.py +393 -0
- graph_agents_cli/scaffold/agents/langgraph/uv-fastapi.lock +2084 -0
- graph_agents_cli/scaffold/agents/langgraph/uv-langgraph-server.lock +2106 -0
- graph_agents_cli/scaffold/agents/langgraph/{{cookiecutter.agent_guidance_filename}} +129 -0
- graph_agents_cli/scaffold/base_templates/_shared/graph-agents-cli-manifest.yaml +36 -0
- graph_agents_cli/scaffold/base_templates/python/.dockerignore +32 -0
- graph_agents_cli/scaffold/base_templates/python/.github/CODEOWNERS +30 -0
- graph_agents_cli/scaffold/base_templates/python/.github/agent.env +7 -0
- graph_agents_cli/scaffold/base_templates/python/.github/workflows/pr_checks.yaml +214 -0
- graph_agents_cli/scaffold/base_templates/python/.gitignore +209 -0
- graph_agents_cli/scaffold/base_templates/python/tests/unit/test_dummy.py +23 -0
- graph_agents_cli/scaffold/base_templates/python/{{cookiecutter.agent_guidance_filename}} +35 -0
- graph_agents_cli/scaffold/cmd_scaffold_group.py +49 -0
- graph_agents_cli/scaffold/commands/__init__.py +13 -0
- graph_agents_cli/scaffold/commands/create.py +1424 -0
- graph_agents_cli/scaffold/commands/enhance.py +1652 -0
- graph_agents_cli/scaffold/commands/upgrade.py +570 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/.github/agent.env +12 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/.github/workflows/promote-to-prod.yaml +371 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/.github/workflows/staging.yaml +450 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/argocd/application-dev.yaml +43 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/argocd/application-prod.yaml +41 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/argocd/application-staging.yaml +43 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/.helmignore +14 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/Chart.yaml +21 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/examples/networkpolicy.yaml +103 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/NOTES.txt +48 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/_helpers.tpl +189 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/certificate.yaml +15 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/configmap.yaml +10 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/deployment.yaml +199 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/hpa.yaml +22 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/httproute.yaml +30 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/ingress.yaml +39 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/networkpolicy.yaml +48 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/pdb.yaml +13 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/postgresql-secret.yaml +37 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/service.yaml +15 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/serviceaccount.yaml +13 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/servicemonitor.yaml +42 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/values-dev.yaml +22 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/values-prod.yaml +45 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/values-staging.yaml +29 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/values.yaml +396 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/tests/integration/test_chart.py +269 -0
- graph_agents_cli/scaffold/deployment_targets/none/README.md +5 -0
- graph_agents_cli/scaffold/deployment_targets/none/python/README.md +6 -0
- graph_agents_cli/scaffold/utils/__init__.py +13 -0
- graph_agents_cli/scaffold/utils/backup.py +212 -0
- graph_agents_cli/scaffold/utils/build_record.py +257 -0
- graph_agents_cli/scaffold/utils/cli_options.py +184 -0
- graph_agents_cli/scaffold/utils/fs.py +83 -0
- graph_agents_cli/scaffold/utils/generate_locks.py +214 -0
- graph_agents_cli/scaffold/utils/generation_metadata.py +88 -0
- graph_agents_cli/scaffold/utils/keyedit.py +768 -0
- graph_agents_cli/scaffold/utils/keymerge.py +537 -0
- graph_agents_cli/scaffold/utils/language.py +138 -0
- graph_agents_cli/scaffold/utils/lock_utils.py +94 -0
- graph_agents_cli/scaffold/utils/logging.py +77 -0
- graph_agents_cli/scaffold/utils/manifest.py +292 -0
- graph_agents_cli/scaffold/utils/merge.py +970 -0
- graph_agents_cli/scaffold/utils/merge3.py +216 -0
- graph_agents_cli/scaffold/utils/openapi_seed.py +199 -0
- graph_agents_cli/scaffold/utils/remote_template.py +376 -0
- graph_agents_cli/scaffold/utils/template.py +1352 -0
- graph_agents_cli/scaffold/utils/upgrade.py +894 -0
- graph_agents_cli/scaffold/utils/version.py +438 -0
- graph_agents_cli/secrets/__init__.py +15 -0
- graph_agents_cli/secrets/_apply.py +954 -0
- graph_agents_cli/secrets/_required.py +188 -0
- graph_agents_cli/secrets/cmd_secrets.py +211 -0
- graph_agents_cli/setup/__init__.py +13 -0
- graph_agents_cli/setup/_antigravity.py +221 -0
- graph_agents_cli/setup/cmd_auth.py +1030 -0
- graph_agents_cli/setup/cmd_dev_token.py +513 -0
- graph_agents_cli/setup/cmd_setup.py +428 -0
- graph_agents_cli/setup/cmd_update.py +140 -0
- graph_agents_cli/skills/__init__.py +13 -0
- graph_agents_cli/skills/_bundle.py +65 -0
- graph_agents_cli/skills/data/README.md +19 -0
- graph_agents_cli/skills/data/graph-agents-cli-deploy/SKILL.md +357 -0
- graph_agents_cli/skills/data/graph-agents-cli-deploy/references/github-settings.md +113 -0
- graph_agents_cli/skills/data/graph-agents-cli-deploy/references/gitops.md +137 -0
- graph_agents_cli/skills/data/graph-agents-cli-deploy/references/kubernetes.md +315 -0
- graph_agents_cli/skills/data/graph-agents-cli-deploy/references/secrets.md +160 -0
- graph_agents_cli/skills/data/graph-agents-cli-eval/SKILL.md +303 -0
- graph_agents_cli/skills/data/graph-agents-cli-eval/references/dataset_schema.md +282 -0
- graph_agents_cli/skills/data/graph-agents-cli-eval/references/metrics-guide.md +143 -0
- graph_agents_cli/skills/data/graph-agents-cli-langgraph-code/SKILL.md +659 -0
- graph_agents_cli/skills/data/graph-agents-cli-langgraph-code/references/langchain-models.md +124 -0
- graph_agents_cli/skills/data/graph-agents-cli-langgraph-code/references/langgraph.md +235 -0
- graph_agents_cli/skills/data/graph-agents-cli-langgraph-code/references/template-contract.md +477 -0
- graph_agents_cli/skills/data/graph-agents-cli-observability/SKILL.md +231 -0
- graph_agents_cli/skills/data/graph-agents-cli-observability/references/langsmith.md +46 -0
- graph_agents_cli/skills/data/graph-agents-cli-observability/references/otel.md +59 -0
- graph_agents_cli/skills/data/graph-agents-cli-scaffold/SKILL.md +414 -0
- graph_agents_cli/skills/data/graph-agents-cli-scaffold/references/flags.md +134 -0
- graph_agents_cli/skills/data/graph-agents-cli-workflow/SKILL.md +478 -0
- graph_agents_cli/skills/data/graph-agents-cli-workflow/references/brainstorming.md +118 -0
- graph_agents_cli/skills/data/graph-agents-cli-workflow/references/commands.md +419 -0
- graph_agents_cli/skills/data/graph-agents-cli-workflow/references/extension.md +156 -0
- graph_agents_cli/skills/data/graph-agents-cli-workflow/references/internals.md +67 -0
- graph_agents_cli/skills/data/graph-agents-cli-workflow/references/spec-template.md +56 -0
- graph_agents_cli/skills/data/graph-agents-cli-workflow/references/terminology.md +119 -0
- graph_agents_cli/system/__init__.py +15 -0
- graph_agents_cli/system/_apply.py +519 -0
- graph_agents_cli/system/_checks.py +1023 -0
- graph_agents_cli/system/_deploy.py +215 -0
- graph_agents_cli/system/_model.py +363 -0
- graph_agents_cli/system/_system.py +664 -0
- graph_agents_cli/system/_views.py +208 -0
- graph_agents_cli/system/cmd_system.py +423 -0
- graph_agents_cli-0.3.1.dist-info/METADATA +162 -0
- graph_agents_cli-0.3.1.dist-info/RECORD +291 -0
- graph_agents_cli-0.3.1.dist-info/WHEEL +4 -0
- graph_agents_cli-0.3.1.dist-info/entry_points.txt +2 -0
- graph_agents_cli-0.3.1.dist-info/licenses/LICENSE +201 -0
- 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
|
+
```
|