langgraph-api 0.16.0.dev8__tar.gz → 0.16.0.dev9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/PKG-INFO +1 -1
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/README.md +17 -4
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/benchmark-runners/delta_turns.ts +17 -2
- langgraph_api-0.16.0.dev9/benchmark/delta-report.js +546 -0
- langgraph_api-0.16.0.dev9/benchmark/delta-report.test.js +155 -0
- langgraph_api-0.16.0.dev9/langgraph_api/__init__.py +1 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/api/__init__.py +53 -8
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/api/runs.py +73 -43
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/api/store.py +40 -14
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/js/client.mts +8 -1
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/js/remote.py +95 -10
- langgraph_api-0.16.0.dev9/langgraph_api/js/src/auth-mutations.mts +71 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/lc_security/transport.py +1 -1
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/schema.py +2 -0
- langgraph_api-0.16.0.dev8/langgraph_api/__init__.py +0 -1
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/.gitignore +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/LICENSE +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/Makefile +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/README.md +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/.gitignore +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/Makefile +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/benchmark-runners/assistant.ts +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/benchmark-runners/benchmark-runner.ts +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/benchmark-runners/benchmark_profiles.ts +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/benchmark-runners/benchmarks.ts +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/benchmark-runners/cancel_first_second_completes.ts +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/benchmark-runners/classify-error.test.js +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/benchmark-runners/classify-error.ts +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/benchmark-runners/delta-turns-lib.test.js +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/benchmark-runners/delta-turns-lib.ts +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/benchmark-runners/enqueued_runs_order.ts +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/benchmark-runners/interrupt_resume.ts +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/benchmark-runners/log-failure.ts +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/benchmark-runners/meta_workload.ts +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/benchmark-runners/store.ts +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/benchmark-runners/stream_write.ts +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/benchmark-runners/thread.ts +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/benchmark-runners/thread_runs_metadata_search.ts +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/benchmark-runners/threads_search_metadata.ts +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/benchmark-runners/types.ts +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/benchmark-runners/wait_write.ts +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/capacity-ci.js +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/capacity-ci.test.js +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/clean-cli.js +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/clean.js +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/continuous/README.md +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/continuous/pyproject.toml +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/continuous/runner.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/continuous/uv.lock +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/daily-report.js +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/daily-report.test.js +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/graphs.js +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/history.js +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/history.test.js +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/host-v2.js +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/host-v2.test.js +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/multer.test.js +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/package.json +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/ramp.js +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/reporting/dd_reporting.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/runner-descriptions.js +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/server-metrics.js +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/server-metrics.test.js +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/slack.js +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/staircase.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/staircase_step_k6.js +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/tsconfig.json +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/update-revision.js +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/weather.js +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/yarn.lock +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/constraints.txt +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/custom_store.sql +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/forbidden.txt +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/hatch_build.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/healthcheck.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph-cloud-debugging-20260210132856.zip +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/_checkpointer/__init__.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/_checkpointer/_adapter.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/_checkpointer/protocol.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/_factory_utils.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/api/a2a.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/api/a2ui.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/api/a2ui_schemas/README.md +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/api/a2ui_schemas/client_capabilities.json +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/api/a2ui_schemas/client_data_model.json +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/api/a2ui_schemas/client_to_server.json +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/api/a2ui_schemas/server_to_client.json +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/api/assistants.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/api/event_streaming.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/api/mcp/__init__.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/api/mcp/_constants.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/api/mcp/_handlers.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/api/mcp/_models.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/api/mcp/_routes.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/api/mcp/_sanitizers.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/api/meta.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/api/openapi.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/api/profile.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/api/threads.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/api/ui.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/asgi_transport.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/asyncio.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/auth/__init__.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/auth/custom.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/auth/errors.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/auth/langsmith/__init__.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/auth/langsmith/backend.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/auth/langsmith/client.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/auth/middleware.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/auth/noop.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/auth/studio_user.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/beacon_data_plane_jwt_auth.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/cache.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/cli.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/command.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/config/__init__.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/config/_parse.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/config/schemas.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/cron_scheduler.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/encryption/__init__.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/encryption/aes_json.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/encryption/context.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/encryption/custom.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/encryption/middleware.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/encryption/shared.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/errors.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/event_streaming/__init__.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/event_streaming/capabilities.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/event_streaming/constants.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/event_streaming/event_normalizers.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/event_streaming/namespace.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/event_streaming/service.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/event_streaming/session.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/event_streaming/state_normalizers.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/event_streaming/types.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/feature_flags.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/graph.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/grpc/__init__.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/grpc/client.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/grpc/generated/core_api_pb2.pyi +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/grpc/ops/__init__.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/grpc/ops/assistants.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/grpc/ops/cache.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/grpc/ops/crons.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/grpc/ops/runs.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/grpc/ops/threads.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/grpc/server.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/grpc/servicers/__init__.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/grpc/servicers/checkpointer.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/grpc/servicers/encryption.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/http.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/http_metrics.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/http_metrics_utils.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/js/.gitignore +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/js/.prettierrc +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/js/.yarnclean +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/js/__init__.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/js/base.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/js/build.mts +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/js/client.http.mts +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/js/errors.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/js/global.d.ts +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/js/package.json +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/js/schema.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/js/src/graph.mts +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/js/src/load.hooks.mjs +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/js/src/preload.mjs +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/js/src/utils/experiment-tracing.mts +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/js/src/utils/files.mts +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/js/src/utils/importMap.mts +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/js/src/utils/pythonSchemas.mts +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/js/src/utils/serde.mts +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/js/sse.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/js/traceblock.mts +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/js/tsconfig.json +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/js/ui.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/js/yarn.lock +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/lc_security/__init__.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/lc_security/exceptions.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/lc_security/policy.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/logging.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/metadata.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/metrics_collector.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/metrics_otlp.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/middleware/__init__.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/middleware/ensure_store.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/middleware/http_logger.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/middleware/private_network.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/middleware/request_id.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/models/__init__.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/models/run.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/otel_context.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/patch.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/queue_entrypoint.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/release_tags.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/route.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/runner/__init__.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/runner/__main__.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/self_hosted_logs.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/self_hosted_metrics.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/serde.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/server.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/sse.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/state.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/store.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/stream.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/stream_v2.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/timing/__init__.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/timing/profiler.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/timing/timer.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/traceblock.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/tracing_session.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/tunneling/cloudflare.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/utils/__init__.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/utils/cache.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/utils/config.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/utils/errors.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/utils/extract.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/utils/future.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/utils/headers.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/utils/network.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/utils/retriable_client.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/utils/stream_codec.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/utils/uuids.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/validation.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/webhook.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/worker.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_api/worker_runtime.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/__init__.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/checkpointer.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/conversion/__init__.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/conversion/_compat.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/conversion/checkpoint.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/conversion/config.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/conversion/durability.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/conversion/store.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/conversion/struct.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/conversion/value.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/__init__.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/checkpointer_pb2.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/checkpointer_pb2.pyi +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/checkpointer_pb2_grpc.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/checkpointer_pb2_grpc.pyi +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/core_api_pb2.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/core_api_pb2.pyi +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/core_api_pb2_grpc.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/core_api_pb2_grpc.pyi +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/encryption_pb2.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/encryption_pb2.pyi +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/encryption_pb2_grpc.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/encryption_pb2_grpc.pyi +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/engine_common_pb2.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/engine_common_pb2.pyi +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/engine_common_pb2_grpc.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/engine_common_pb2_grpc.pyi +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/enum_cancel_run_action_pb2.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/enum_cancel_run_action_pb2.pyi +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/enum_cancel_run_action_pb2_grpc.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/enum_cancel_run_action_pb2_grpc.pyi +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/enum_control_signal_pb2.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/enum_control_signal_pb2.pyi +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/enum_control_signal_pb2_grpc.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/enum_control_signal_pb2_grpc.pyi +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/enum_cron_on_run_completed_pb2.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/enum_cron_on_run_completed_pb2.pyi +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/enum_cron_on_run_completed_pb2_grpc.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/enum_cron_on_run_completed_pb2_grpc.pyi +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/enum_durability_pb2.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/enum_durability_pb2.pyi +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/enum_durability_pb2_grpc.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/enum_durability_pb2_grpc.pyi +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/enum_multitask_strategy_pb2.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/enum_multitask_strategy_pb2.pyi +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/enum_multitask_strategy_pb2_grpc.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/enum_multitask_strategy_pb2_grpc.pyi +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/enum_run_status_pb2.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/enum_run_status_pb2.pyi +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/enum_run_status_pb2_grpc.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/enum_run_status_pb2_grpc.pyi +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/enum_stream_mode_pb2.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/enum_stream_mode_pb2.pyi +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/enum_stream_mode_pb2_grpc.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/enum_stream_mode_pb2_grpc.pyi +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/enum_thread_status_pb2.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/enum_thread_status_pb2.pyi +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/enum_thread_status_pb2_grpc.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/enum_thread_status_pb2_grpc.pyi +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/enum_thread_stream_mode_pb2.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/enum_thread_stream_mode_pb2.pyi +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/enum_thread_stream_mode_pb2_grpc.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/enum_thread_stream_mode_pb2_grpc.pyi +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/errors_pb2.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/errors_pb2.pyi +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/errors_pb2_grpc.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/errors_pb2_grpc.pyi +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/runner_pb2.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/runner_pb2.pyi +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/runner_pb2_grpc.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/runner_pb2_grpc.pyi +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/store_pb2.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/store_pb2.pyi +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/store_pb2_grpc.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/proto/store_pb2_grpc.pyi +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/serde.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_grpc_common/store.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_license/__init__.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_license/validation.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_runtime/__init__.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_runtime/checkpoint.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_runtime/database.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_runtime/lifespan.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_runtime/metrics.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_runtime/ops.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_runtime/queue.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_runtime/retry.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_runtime/routes.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/langgraph_runtime/store.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/logging.json +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/openapi.json +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/pyproject.toml +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/scripts/build_wheel.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/scripts/create_license.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/scripts/run_a2a_tck.py +0 -0
- {langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/uv.lock +0 -0
|
@@ -129,7 +129,11 @@ by replaying them. That moves cost from write to read, and the read cost grows w
|
|
|
129
129
|
thread depth. `delta_turns` measures it: one thread per VU, one turn per iteration,
|
|
130
130
|
`turn_duration` tagged by depth bucket.
|
|
131
131
|
|
|
132
|
-
|
|
132
|
+
`benchmark-delta.yml` runs four cells nightly, 20 users for 25 minutes each: plain channel, then
|
|
133
|
+
delta snapshotting every 50 (`benchmark_delta_freq`, the deepagents setting), 500
|
|
134
|
+
(`benchmark_delta_500`) and 1000 (`benchmark_delta`, the library default). `delta-report.js` posts
|
|
135
|
+
a headline table (non-delta vs delta at 50: read, write, storage by depth) and one table per metric
|
|
136
|
+
across the three frequencies. The messages and migration cells stay manual. Run the cells one at a time and compare `turn_duration` by depth. Both sides get slower
|
|
133
137
|
with depth for different reasons, the control by re-writing a growing value and delta by
|
|
134
138
|
replaying more writes, so the deliverable is which curve is worse and where they cross.
|
|
135
139
|
|
|
@@ -138,6 +142,7 @@ replaying more writes, so the deliverable is which curve is worse and where they
|
|
|
138
142
|
| control | `GRAPH_ID=benchmark EXPECT_DELTA=false` |
|
|
139
143
|
| delta, snapshot every 1000 | `GRAPH_ID=benchmark_delta` |
|
|
140
144
|
| delta, snapshot every 50 | `GRAPH_ID=benchmark_delta_freq` |
|
|
145
|
+
| delta, snapshot every 500 | `GRAPH_ID=benchmark_delta_500` |
|
|
141
146
|
| delta on a message list | `GRAPH_ID=benchmark_delta_messages PAYLOAD=messages` |
|
|
142
147
|
| migration | `GRAPH_ID=benchmark_delta MIGRATE_FROM_GRAPH_ID=benchmark MIGRATE_AFTER=200` |
|
|
143
148
|
|
|
@@ -154,7 +159,7 @@ Knobs beyond the common ones:
|
|
|
154
159
|
- `MIGRATE_FROM_GRAPH_ID` / `MIGRATE_AFTER` - run the first N turns on another graph, then
|
|
155
160
|
switch on the same thread.
|
|
156
161
|
- `VERIFY_EVERY` - cold re-read cadence in turns. Default 50, `0` disables.
|
|
157
|
-
- `DEPTH_BUCKET` - width of a depth bucket.
|
|
162
|
+
- `DEPTH_BUCKET` - width of a depth bucket. Defaults to `VERIFY_EVERY`, so each bucket holds one cold read per thread.
|
|
158
163
|
|
|
159
164
|
Two things to know before reading any result:
|
|
160
165
|
|
|
@@ -166,7 +171,9 @@ Two things to know before reading any result:
|
|
|
166
171
|
|
|
167
172
|
The runner fails a turn rather than reporting a false pass when delta is not actually on:
|
|
168
173
|
the `DeltaChannel` import is version-guarded, and its stub degrades to a plain channel.
|
|
169
|
-
At every `VERIFY_EVERY` turn it also
|
|
174
|
+
At every `VERIFY_EVERY` turn it also times the cold `GET /threads/{thread_id}/state` as
|
|
175
|
+
`state_read_duration` (the read path alone; a turn is one save plus the read before it), and
|
|
176
|
+
reads `GET /threads/{thread_id}/storage` and records
|
|
170
177
|
`thread_storage_bytes` tagged by depth bucket and graph, so the control and delta cells can
|
|
171
178
|
be compared on the bytes they occupy, which is what the feature exists to reduce.
|
|
172
179
|
|
|
@@ -185,6 +192,12 @@ be compared on the bytes they occupy, which is what the feature exists to reduce
|
|
|
185
192
|
|
|
186
193
|
- Daily ramp benchmark: `.github/workflows/benchmark.yml`
|
|
187
194
|
- Daily capacity matrix: `.github/workflows/benchmark-capacity-matrix.yml`
|
|
195
|
+
- Daily delta-channel cells: `.github/workflows/benchmark-delta.yml`, reported by `delta-report.js`
|
|
196
|
+
|
|
197
|
+
The staging deployments build their graphs from
|
|
198
|
+
[langgraph-server-benchmarks](https://github.com/langchain-ai/langgraph-server-benchmarks), not
|
|
199
|
+
from this repo. Its `src/agent/graph.py` is a copy of `api/tests/graphs/benchmark.py`; a graph
|
|
200
|
+
change here reaches the nightly only once that copy is updated.
|
|
188
201
|
|
|
189
202
|
### Capacity matrix setup
|
|
190
203
|
|
|
@@ -203,7 +216,7 @@ Every report follows one shape so the channel reads the same way each morning, o
|
|
|
203
216
|
- One table per deployment, about 40 characters wide so it fits a phone, with `▲ better` / `▼ worse` markers on cells that left their usual range.
|
|
204
217
|
- One legend line (pass rule, arrows, dot colors), then a link to the thread, which holds the per-cell or per-run numbers and the longer *How to read*, and to the run's artifacts.
|
|
205
218
|
|
|
206
|
-
Both reports compare tonight with the previous scheduled nights (`history.js`, artifacts fetched with `gh`). A figure is flagged only when it leaves that cell's own observed range, and only once five same-kind nights exist; before that the table says how many nights it has. The daily benchmark posts one digest per workflow once its last runner finishes (`daily-report.js`, fed by each runner's artifact), not one message per runner.
|
|
219
|
+
Both reports compare tonight with the previous scheduled nights (`history.js`, artifacts fetched with `gh`). A figure is flagged only when it leaves that cell's own observed range, and only once five same-kind nights exist; before that the table says how many nights it has. The daily benchmark posts one digest per workflow once its last runner finishes (`daily-report.js`, fed by each runner's artifact), not one message per runner. The delta-channel report (`delta-report.js`) keeps the plain-vs-delta table in the channel and puts the snapshot-frequency sweep in the thread.
|
|
207
220
|
|
|
208
221
|
Local commands:
|
|
209
222
|
|
{langgraph_api-0.16.0.dev8 → langgraph_api-0.16.0.dev9}/benchmark/benchmark-runners/delta_turns.ts
RENAMED
|
@@ -30,6 +30,8 @@ const PAYLOAD_CHANNELS = [
|
|
|
30
30
|
|
|
31
31
|
/** Tagged by depth: VUs ramp in, so clock time is not thread length. */
|
|
32
32
|
const turnDuration = new Trend('turn_duration', true);
|
|
33
|
+
/** The read path alone: a cold GET /state replays the channel without writing. */
|
|
34
|
+
const stateReadDuration = new Trend('state_read_duration', true);
|
|
33
35
|
const threadStorageBytes = new Trend('thread_storage_bytes', false);
|
|
34
36
|
|
|
35
37
|
const TURN_CONFIG: Omit<DeltaTurnsOptions, 'graphId'> = {
|
|
@@ -37,7 +39,8 @@ const TURN_CONFIG: Omit<DeltaTurnsOptions, 'graphId'> = {
|
|
|
37
39
|
migrateAfter: parseInt(__ENV.MIGRATE_AFTER || '0', 10),
|
|
38
40
|
expectDelta: (__ENV.EXPECT_DELTA || 'true') === 'true',
|
|
39
41
|
verifyEvery: parseInt(__ENV.VERIFY_EVERY || '50', 10),
|
|
40
|
-
|
|
42
|
+
// One bucket per cold read, so every row of the report is one sample per thread.
|
|
43
|
+
bucketSize: parseInt(__ENV.DEPTH_BUCKET || __ENV.VERIFY_EVERY || '50', 10),
|
|
41
44
|
};
|
|
42
45
|
|
|
43
46
|
// Each k6 VU runs this module in its own context, so these are per VU.
|
|
@@ -96,7 +99,13 @@ export class DeltaTurns extends BenchmarkRunner {
|
|
|
96
99
|
// A failed body is not state, and comparing against it would report drift
|
|
97
100
|
// on top of the failure that caused it.
|
|
98
101
|
if (response.status === 200 && (plan.guard || plan.verify)) {
|
|
102
|
+
const readStartedAt = Date.now();
|
|
99
103
|
const state = http.get(`${baseUrl}/threads/${threadId}/state`, requestParams);
|
|
104
|
+
// The turn-2 guard read is at depth 2, not the bucket's end; timing it
|
|
105
|
+
// would pull the first row's read median down.
|
|
106
|
+
if (state.status === 200 && plan.verify) {
|
|
107
|
+
stateReadDuration.add(Date.now() - readStartedAt, { depth: plan.depthBucket });
|
|
108
|
+
}
|
|
100
109
|
addResponse(responses, 'state', state);
|
|
101
110
|
const body = state.status === 200 ? safeJson(state.body) : null;
|
|
102
111
|
if (body === null) {
|
|
@@ -111,7 +120,13 @@ export class DeltaTurns extends BenchmarkRunner {
|
|
|
111
120
|
}
|
|
112
121
|
}
|
|
113
122
|
if (plan.verify) {
|
|
114
|
-
|
|
123
|
+
// Servers before 0.16 have no storage endpoint. A 404 there is a missing
|
|
124
|
+
// sample, not a failed request: without this, one 404 per 50 turns is
|
|
125
|
+
// about 2% and trips the 1% error budget on every cell.
|
|
126
|
+
const storage = http.get(`${baseUrl}/threads/${threadId}/storage`, {
|
|
127
|
+
...requestParams,
|
|
128
|
+
responseCallback: http.expectedStatuses(200, 404),
|
|
129
|
+
});
|
|
115
130
|
addResponse(responses, 'storage', storage);
|
|
116
131
|
const total = storage.status === 200 ? safeJson(storage.body)?.total_bytes : null;
|
|
117
132
|
if (typeof total === 'number') {
|
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The nightly delta-channel report. Reads the k6 raw sample files because the
|
|
3
|
+
* per-depth numbers live only in sample tags; the summary has aggregates for
|
|
4
|
+
* the whole run.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createReadStream, existsSync, mkdirSync, readdirSync, statSync, writeFileSync } from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import readline from 'node:readline';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
|
|
12
|
+
import { ERROR_ATTENTION, MIN_SUCCESS_RATE, describeThreshold, errorParts, findLatestSummary, isHealthy, readApiVersion, readSummary } from './daily-report.js';
|
|
13
|
+
import { MIN_HISTORY_NIGHTS, compare, flaggedChange, formatDelta, formatMove } from './history.js';
|
|
14
|
+
import { dotFor, postReport } from './slack.js';
|
|
15
|
+
|
|
16
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
|
|
18
|
+
/** `cell` is the matrix key and the artifact infix. Snapshot frequency is fixed per graph. */
|
|
19
|
+
export const CELLS = [
|
|
20
|
+
{ cell: 'control', graph: 'benchmark', column: 'non-delta', label: 'plain channel', snapshot: null },
|
|
21
|
+
{ cell: 'delta_50', graph: 'benchmark_delta_freq', column: '50', label: 'delta, snapshot every 50 (deepagents)', snapshot: 50 },
|
|
22
|
+
{ cell: 'delta_500', graph: 'benchmark_delta_500', column: '500', label: 'delta, snapshot every 500', snapshot: 500 },
|
|
23
|
+
{ cell: 'delta_1000', graph: 'benchmark_delta', column: '1000', label: 'delta, snapshot every 1000 (library default)', snapshot: 1000 },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
/** The channel table compares against the setting customers actually run. */
|
|
27
|
+
export const HEADLINE = ['control', 'delta_50'];
|
|
28
|
+
|
|
29
|
+
/** Metric key, label, printer. All three are lower-is-better. */
|
|
30
|
+
const METRICS = [
|
|
31
|
+
['readP50Ms', 'read', (v) => (isNumber(v) ? (v / 1000).toFixed(2) : '-')],
|
|
32
|
+
['saveEstMs', 'write', (v) => (isNumber(v) ? (v / 1000).toFixed(2) : '-')],
|
|
33
|
+
['bytes', 'KB', (v) => (isNumber(v) ? String(Math.round(v / 1024)) : '-')],
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
function isNumber(value) {
|
|
37
|
+
return typeof value === 'number' && Number.isFinite(value);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function count(value) {
|
|
41
|
+
return isNumber(value) ? Math.round(value).toLocaleString('en-US') : 'n/a';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function percentile(sorted, p) {
|
|
45
|
+
if (sorted.length === 0) return null;
|
|
46
|
+
return sorted[Math.min(sorted.length - 1, Math.max(0, Math.ceil(sorted.length * p) - 1))];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function bucketStart(bucket) {
|
|
50
|
+
const n = parseInt(String(bucket).split('-')[0], 10);
|
|
51
|
+
return Number.isFinite(n) ? n : Number.MAX_SAFE_INTEGER;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const byDepth = (a, b) => bucketStart(a) - bucketStart(b);
|
|
55
|
+
|
|
56
|
+
/** Rows are labelled by the turn they end on, where the cold read and storage read happen. */
|
|
57
|
+
function depthLabel(bucket) {
|
|
58
|
+
return String(bucket).split('-')[1] ?? String(bucket);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function bucketEnd(bucket) {
|
|
62
|
+
const n = parseInt(depthLabel(bucket), 10);
|
|
63
|
+
return Number.isFinite(n) ? n : 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Every 250 turns is enough to see a slope; the first and deepest rows anchor it. */
|
|
67
|
+
export const HEADLINE_EVERY = 250;
|
|
68
|
+
|
|
69
|
+
/** `keep` adds rows that must show regardless of spacing: the ones carrying a flagged change. */
|
|
70
|
+
export function headlineRows(depths, keep = []) {
|
|
71
|
+
const sorted = [...depths].sort(byDepth);
|
|
72
|
+
const last = sorted.at(-1);
|
|
73
|
+
const kept = new Set(keep);
|
|
74
|
+
return sorted.filter((d, i) => i === 0 || d === last || kept.has(d) || bucketEnd(d) % HEADLINE_EVERY === 0);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The turn before each snapshot and the snapshot turn are the peaks and resets
|
|
79
|
+
* of the read sawtooth; the rows in between only repeat the slope.
|
|
80
|
+
*/
|
|
81
|
+
export function sweepRows(depths, frequencies) {
|
|
82
|
+
const sorted = [...depths].sort(byDepth);
|
|
83
|
+
if (sorted.length === 0) return [];
|
|
84
|
+
const width = bucketEnd(sorted[0]) - bucketStart(sorted[0]) + 1;
|
|
85
|
+
const last = bucketEnd(sorted.at(-1));
|
|
86
|
+
const wanted = new Set([bucketEnd(sorted[0]), last]);
|
|
87
|
+
for (const f of frequencies.filter((f) => isNumber(f) && f > width)) {
|
|
88
|
+
for (let turn = f; turn <= last; turn += f) {
|
|
89
|
+
wanted.add(turn - width);
|
|
90
|
+
wanted.add(turn);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return sorted.filter((d) => wanted.has(bucketEnd(d)));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* A turn is read + step + save and cannot be split from outside, so the save
|
|
98
|
+
* is what is left after the cold read. It still carries the run's fixed
|
|
99
|
+
* overhead (queue, worker), which is the same in every cell, so it compares
|
|
100
|
+
* across cells and is not an absolute.
|
|
101
|
+
*/
|
|
102
|
+
export function saveEstimate(turnP50Ms, readP50Ms) {
|
|
103
|
+
if (!isNumber(turnP50Ms) || !isNumber(readP50Ms)) return null;
|
|
104
|
+
return Math.max(0, turnP50Ms - readP50Ms);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Bytes keep the largest reading in the bucket: the thread only grows. */
|
|
108
|
+
export function aggregateSamples(points) {
|
|
109
|
+
const turns = {};
|
|
110
|
+
const reads = {};
|
|
111
|
+
const bytes = {};
|
|
112
|
+
for (const point of points) {
|
|
113
|
+
const depth = point.tags?.depth;
|
|
114
|
+
if (depth == null || !isNumber(point.value)) continue;
|
|
115
|
+
if (point.metric === 'turn_duration') (turns[depth] ??= []).push(point.value);
|
|
116
|
+
else if (point.metric === 'state_read_duration') (reads[depth] ??= []).push(point.value);
|
|
117
|
+
else if (point.metric === 'thread_storage_bytes') bytes[depth] = Math.max(bytes[depth] ?? 0, point.value);
|
|
118
|
+
}
|
|
119
|
+
const buckets = {};
|
|
120
|
+
for (const depth of new Set([...Object.keys(turns), ...Object.keys(reads), ...Object.keys(bytes)])) {
|
|
121
|
+
const sorted = (turns[depth] ?? []).sort((a, b) => a - b);
|
|
122
|
+
const sortedReads = (reads[depth] ?? []).sort((a, b) => a - b);
|
|
123
|
+
const p50Ms = percentile(sorted, 0.5);
|
|
124
|
+
// One cold read per thread per VERIFY_EVERY turns is few samples, so the median only.
|
|
125
|
+
const readP50Ms = percentile(sortedReads, 0.5);
|
|
126
|
+
buckets[depth] = {
|
|
127
|
+
turns: sorted.length,
|
|
128
|
+
p50Ms,
|
|
129
|
+
p95Ms: percentile(sorted, 0.95),
|
|
130
|
+
readP50Ms,
|
|
131
|
+
saveEstMs: saveEstimate(p50Ms, readP50Ms),
|
|
132
|
+
bytes: bytes[depth] ?? null,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
return buckets;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function readSamples(file) {
|
|
139
|
+
const points = [];
|
|
140
|
+
const lines = readline.createInterface({ input: createReadStream(file), crlfDelay: Infinity });
|
|
141
|
+
for await (const line of lines) {
|
|
142
|
+
if (!line.includes('"Point"')) continue;
|
|
143
|
+
let entry;
|
|
144
|
+
try {
|
|
145
|
+
entry = JSON.parse(line);
|
|
146
|
+
} catch {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (!['turn_duration', 'state_read_duration', 'thread_storage_bytes'].includes(entry.metric)) continue;
|
|
150
|
+
points.push({ metric: entry.metric, value: entry.data?.value, tags: entry.data?.tags });
|
|
151
|
+
}
|
|
152
|
+
return points;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function newestRawData(dir) {
|
|
156
|
+
try {
|
|
157
|
+
return (
|
|
158
|
+
readdirSync(dir, { recursive: true })
|
|
159
|
+
.filter((name) => path.basename(name).startsWith('raw_data_') && name.endsWith('.json'))
|
|
160
|
+
.map((name) => path.join(dir, name))
|
|
161
|
+
.sort((a, b) => statSync(b).mtimeMs - statSync(a).mtimeMs)[0] ?? null
|
|
162
|
+
);
|
|
163
|
+
} catch {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* One directory per cell, as `actions/download-artifact` lays them out without
|
|
170
|
+
* merging. A cell with no directory never ran.
|
|
171
|
+
*/
|
|
172
|
+
export async function collectCells(resultsDir) {
|
|
173
|
+
const dirs = existsSync(resultsDir)
|
|
174
|
+
? readdirSync(resultsDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name)
|
|
175
|
+
: [];
|
|
176
|
+
const cells = [];
|
|
177
|
+
for (const spec of CELLS) {
|
|
178
|
+
const name = dirs.find((dir) => dir.startsWith(`delta-results-${spec.cell}-`));
|
|
179
|
+
const dir = name ? path.join(resultsDir, name) : null;
|
|
180
|
+
const context = dir ? readSummary(path.join(dir, 'report_context.json')) ?? {} : {};
|
|
181
|
+
const raw = dir ? newestRawData(dir) : null;
|
|
182
|
+
cells.push({
|
|
183
|
+
...spec,
|
|
184
|
+
stepOutcome: context.stepOutcome ?? (dir ? 'unknown' : 'missing'),
|
|
185
|
+
summary: dir ? readSummary(findLatestSummary(dir)) : null,
|
|
186
|
+
load: { loadSize: context.loadSize, plateauDuration: context.plateauDuration },
|
|
187
|
+
buckets: raw ? aggregateSamples(await readSamples(raw)) : {},
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
return cells;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function loadDeltaHistory(dir) {
|
|
194
|
+
const nights = [];
|
|
195
|
+
let runs;
|
|
196
|
+
try {
|
|
197
|
+
runs = readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory());
|
|
198
|
+
} catch {
|
|
199
|
+
return nights;
|
|
200
|
+
}
|
|
201
|
+
for (const run of runs) {
|
|
202
|
+
let files;
|
|
203
|
+
try {
|
|
204
|
+
files = readdirSync(path.join(dir, run.name), { recursive: true });
|
|
205
|
+
} catch {
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
const file = files.find((name) => path.basename(name) === 'delta_report.json');
|
|
209
|
+
const report = file ? readSummary(path.join(dir, run.name, file)) : null;
|
|
210
|
+
if (report?.cells) nights.push(report.cells);
|
|
211
|
+
}
|
|
212
|
+
return nights;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function healthy(cell) {
|
|
216
|
+
return isHealthy({ stepOutcome: cell.stepOutcome, metrics: cell.summary?.metrics });
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function guardFailures(cell) {
|
|
220
|
+
const n = cell.summary?.metrics?.errors?.missingMessage;
|
|
221
|
+
return isNumber(n) ? n : 0;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function httpErrorCount(cell) {
|
|
225
|
+
const byStatus = cell.summary?.metrics?.errors?.byStatus;
|
|
226
|
+
if (!byStatus || typeof byStatus !== 'object') return 0;
|
|
227
|
+
return Object.values(byStatus).filter(isNumber).reduce((sum, n) => sum + n, 0);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function deepest(buckets) {
|
|
231
|
+
const depths = Object.keys(buckets).filter((d) => buckets[d].turns > 0);
|
|
232
|
+
return depths.length ? depths.sort(byDepth).at(-1) : null;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function failureReason(cell) {
|
|
236
|
+
const metrics = cell.summary?.metrics;
|
|
237
|
+
if (!metrics) return `no results produced, step finished as \`${cell.stepOutcome}\``;
|
|
238
|
+
const crossed = [];
|
|
239
|
+
for (const [metric, expressions] of Object.entries(cell.summary.thresholds ?? {})) {
|
|
240
|
+
for (const [expression, verdict] of Object.entries(expressions ?? {})) {
|
|
241
|
+
if (verdict?.ok === false) crossed.push(describeThreshold(metric, expression, metrics).breach);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (crossed.length > 0) return crossed.join(', ');
|
|
245
|
+
if (!metrics.successfulRuns) return 'no turn succeeded';
|
|
246
|
+
if (isNumber(metrics.successRate) && metrics.successRate <= MIN_SUCCESS_RATE) {
|
|
247
|
+
return `success ${metrics.successRate.toFixed(2)}% (floor ${MIN_SUCCESS_RATE}%)`;
|
|
248
|
+
}
|
|
249
|
+
return `step finished as \`${cell.stepOutcome}\``;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Compared per metric at the deepest turn with a reading tonight and every
|
|
254
|
+
* night: a shallower one would hide a regression that only shows at depth, and
|
|
255
|
+
* a run that stops mid-bucket has turns there but no cold read yet.
|
|
256
|
+
*/
|
|
257
|
+
export function cellChanges(cell, history) {
|
|
258
|
+
const buckets = cell.buckets ?? {};
|
|
259
|
+
const changes = [];
|
|
260
|
+
for (const [key, label, format] of METRICS) {
|
|
261
|
+
const depth = Object.keys(buckets)
|
|
262
|
+
.filter((d) => isNumber(buckets[d][key]) && history.every((night) => isNumber(night[cell.cell]?.buckets?.[d]?.[key])))
|
|
263
|
+
.sort(byDepth)
|
|
264
|
+
.at(-1);
|
|
265
|
+
if (!depth) continue;
|
|
266
|
+
const past = history.map((night) => night[cell.cell].buckets[depth][key]);
|
|
267
|
+
const flagged = flaggedChange(buckets[depth][key], past, { higherIsBetter: false });
|
|
268
|
+
const verdict = flagged ?? compare(buckets[depth][key], past);
|
|
269
|
+
if (!verdict) continue;
|
|
270
|
+
changes.push({
|
|
271
|
+
key,
|
|
272
|
+
label,
|
|
273
|
+
depth,
|
|
274
|
+
flagged: Boolean(flagged),
|
|
275
|
+
better: flagged?.better ?? null,
|
|
276
|
+
arrow: flagged?.arrow ?? null,
|
|
277
|
+
deltaPct: verdict.deltaPct,
|
|
278
|
+
move: formatMove(format(verdict.median), format(buckets[depth][key]), verdict.deltaPct, key === 'bytes' ? 'KB' : 's'),
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
return changes;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function heldFor(plateauDuration) {
|
|
285
|
+
const plateau = Number(plateauDuration);
|
|
286
|
+
if (!isNumber(plateau) || plateau <= 0) return null;
|
|
287
|
+
return plateau % 60 === 0 ? `${plateau / 60} min` : `${plateau}s`;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** Same shape as the daily digest's second line: k6, paths, measures. */
|
|
291
|
+
export function describeDelta(cells) {
|
|
292
|
+
const withLoad = cells.find((c) => Number(c.load?.loadSize) > 0);
|
|
293
|
+
const users = withLoad ? `${count(Number(withLoad.load.loadSize))} users` : 'a few users';
|
|
294
|
+
const held = heldFor(withLoad?.load?.plateauDuration);
|
|
295
|
+
const load = [users, 'one thread each', held ? `${held} hold` : null, 'closed model'].filter(Boolean).join(', ');
|
|
296
|
+
return (
|
|
297
|
+
`k6: ${load}. Paths: one turn per iteration on a growing thread (run and wait), plus a cold state read and a storage read every 50 turns. ` +
|
|
298
|
+
'Cells: plain channel vs DeltaChannel with a snapshot every 50 (deepagents), 500 and 1000 (library default). ' +
|
|
299
|
+
"Measures read, write and checkpoint bytes by thread depth. Same load nightly on the day's revision, so a change is the server."
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function groupName(cell) {
|
|
304
|
+
return cell.cell === 'control' ? 'non-delta' : `delta, snap ${cell.column}`;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export function headlineTable(cells, changesByCell) {
|
|
308
|
+
const pair = HEADLINE.map((key) => cells.find((c) => c.cell === key)).filter(Boolean);
|
|
309
|
+
const flaggedDepths = pair.flatMap((c) => (changesByCell.get(c) ?? []).filter((ch) => ch.flagged).map((ch) => ch.depth));
|
|
310
|
+
// The deepest bucket often has turns but no cold read yet; a row of dashes says nothing.
|
|
311
|
+
const measured = new Set(
|
|
312
|
+
pair.flatMap((c) => Object.keys(c.buckets ?? {}).filter((d) => METRICS.some(([key]) => isNumber(c.buckets[d][key]))))
|
|
313
|
+
);
|
|
314
|
+
const depths = headlineRows(measured, flaggedDepths);
|
|
315
|
+
if (depths.length === 0) return ['*No per-depth samples were recorded.*'];
|
|
316
|
+
const w = 8;
|
|
317
|
+
const units = { read: 'read s', write: 'write s', KB: 'KB' };
|
|
318
|
+
const lines = [
|
|
319
|
+
'```',
|
|
320
|
+
['turn'.padEnd(6), ...pair.map((c) => groupName(c).padStart(w * 3))].join(''),
|
|
321
|
+
[''.padEnd(6), ...pair.flatMap(() => METRICS.map(([, label]) => units[label].padStart(w)))].join(''),
|
|
322
|
+
];
|
|
323
|
+
for (const depth of depths) {
|
|
324
|
+
const row = [depthLabel(depth).padEnd(6)];
|
|
325
|
+
for (const cell of pair) {
|
|
326
|
+
const b = cell.buckets?.[depth];
|
|
327
|
+
for (const [key, , format] of METRICS) {
|
|
328
|
+
const change = changesByCell.get(cell)?.find((c) => c.key === key && c.depth === depth && c.flagged);
|
|
329
|
+
const value = b && b.turns > 0 ? format(b[key]) : '-';
|
|
330
|
+
row.push(`${value}${change?.arrow ?? ''}`.padStart(w));
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
lines.push(row.join(''));
|
|
334
|
+
}
|
|
335
|
+
lines.push('```');
|
|
336
|
+
return lines;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export function sweepTables(cells) {
|
|
340
|
+
const deltas = cells.filter((c) => c.cell !== 'control');
|
|
341
|
+
const depths = sweepRows(new Set(deltas.flatMap((c) => Object.keys(c.buckets ?? {}))), deltas.map((c) => c.snapshot));
|
|
342
|
+
if (depths.length === 0) return [];
|
|
343
|
+
const w = 10;
|
|
344
|
+
const titles = { read: 'read, median seconds', write: 'write, seconds', KB: 'storage, KB at that turn' };
|
|
345
|
+
return METRICS.map(([key, label, format]) => {
|
|
346
|
+
const lines = [`*${titles[label]}*`, '```'];
|
|
347
|
+
lines.push(['turn'.padEnd(6), ...deltas.map((c) => `snap ${c.column}`.padStart(w))].join(''));
|
|
348
|
+
for (const depth of depths) {
|
|
349
|
+
lines.push(
|
|
350
|
+
[depthLabel(depth).padEnd(6), ...deltas.map((c) => (c.buckets?.[depth]?.turns > 0 ? format(c.buckets[depth][key]) : '-').padStart(w))].join('')
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
lines.push('```');
|
|
354
|
+
return lines.join('\n');
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function cellName(cell) {
|
|
359
|
+
return cell.cell === 'control' ? 'non-delta' : `delta/${cell.column}`;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function anyBytes(cells) {
|
|
363
|
+
return cells.some((c) => Object.values(c.buckets ?? {}).some((b) => isNumber(b.bytes)));
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* @param {Array<object>} cells from collectCells
|
|
368
|
+
* @param {{apiVersion?: string|null, artifactUrl?: string|null, now?: Date, manual?: boolean, history?: object[]}} context
|
|
369
|
+
* @returns {{text: string, thread: string[]}}
|
|
370
|
+
*/
|
|
371
|
+
export function buildReport(cells, context = {}) {
|
|
372
|
+
const { apiVersion = null, artifactUrl = null, now = new Date(), manual = false, history = [] } = context;
|
|
373
|
+
|
|
374
|
+
const changesByCell = new Map(cells.map((cell) => [cell, cell.summary?.metrics ? cellChanges(cell, history) : []]));
|
|
375
|
+
const unhealthy = cells.filter((cell) => !healthy(cell));
|
|
376
|
+
const withGuard = cells.filter((cell) => guardFailures(cell) > 0);
|
|
377
|
+
const withHttp = cells.filter((cell) => errorParts(cell.summary?.metrics).length > 0);
|
|
378
|
+
// Same bar as the daily digest: a stray 503 is noted, not escalated. A guard
|
|
379
|
+
// failure is a correctness bug, so one is enough.
|
|
380
|
+
const manyHttp = withHttp.filter((cell) => httpErrorCount(cell) >= ERROR_ATTENTION);
|
|
381
|
+
const regressed = [];
|
|
382
|
+
const improved = [];
|
|
383
|
+
for (const cell of cells) {
|
|
384
|
+
for (const change of changesByCell.get(cell)) {
|
|
385
|
+
if (!change.flagged) continue;
|
|
386
|
+
(change.better ? improved : regressed).push(`${cellName(cell)} ${change.label}@turn ${depthLabel(change.depth)} ${change.move}`);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
const nights = history.length;
|
|
390
|
+
const checked = nights >= MIN_HISTORY_NIGHTS;
|
|
391
|
+
|
|
392
|
+
const clauses = [];
|
|
393
|
+
if (unhealthy.length > 0) clauses.push(`${unhealthy.map(cellName).join(', ')} failed`);
|
|
394
|
+
if (withGuard.length > 0) clauses.push(`correctness check failed in ${withGuard.map(cellName).join(', ')}`);
|
|
395
|
+
if (manyHttp.length > 0) clauses.push(`errors in ${manyHttp.map(cellName).join(', ')}`);
|
|
396
|
+
if (regressed.length > 0) clauses.push(`${regressed.length} regression${regressed.length === 1 ? '' : 's'}`);
|
|
397
|
+
if (clauses.length === 0) clauses.push('all quiet');
|
|
398
|
+
|
|
399
|
+
const dot = dotFor({
|
|
400
|
+
failed: unhealthy.length > 0,
|
|
401
|
+
attention: withGuard.length + manyHttp.length + regressed.length > 0,
|
|
402
|
+
});
|
|
403
|
+
const date = now.toLocaleDateString('en-US', { month: 'short', day: 'numeric', timeZone: 'UTC' });
|
|
404
|
+
const passed = cells.length - unhealthy.length;
|
|
405
|
+
const lines = [
|
|
406
|
+
`${dot} *Delta Channel · ${date}*${manual ? ' · manual run' : ''}${apiVersion ? ` · API ${apiVersion}` : ''} · ${passed} of ${cells.length} cells passed · ${clauses.join(' · ')}`,
|
|
407
|
+
describeDelta(cells),
|
|
408
|
+
];
|
|
409
|
+
if (unhealthy.length > 0) {
|
|
410
|
+
lines.push(`*Failed:* ${unhealthy.map((cell) => `${cellName(cell)}: ${failureReason(cell)}`).join(' · ')}`);
|
|
411
|
+
}
|
|
412
|
+
if (withGuard.length + withHttp.length > 0) {
|
|
413
|
+
const parts = cells
|
|
414
|
+
.filter((cell) => withGuard.includes(cell) || withHttp.includes(cell))
|
|
415
|
+
.map(
|
|
416
|
+
(cell) =>
|
|
417
|
+
`${cellName(cell)} ${[...errorParts(cell.summary.metrics), guardFailures(cell) > 0 ? `correctness check failed × ${count(guardFailures(cell))}` : null].filter(Boolean).join(', ')}`
|
|
418
|
+
);
|
|
419
|
+
lines.push(`*Errors:* ${parts.join(' · ')}`);
|
|
420
|
+
}
|
|
421
|
+
if (!checked) {
|
|
422
|
+
lines.push(`*Regressed:* not checked, ${nights} of ${MIN_HISTORY_NIGHTS} nights of history`);
|
|
423
|
+
} else {
|
|
424
|
+
lines.push(`*Regressed:* ${regressed.length > 0 ? regressed.join(' · ') : 'none'}`);
|
|
425
|
+
if (improved.length > 0) lines.push(`*Improved:* ${improved.join(' · ')}`);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
lines.push(
|
|
429
|
+
'',
|
|
430
|
+
`*Plain channel vs DeltaChannel at the deepagents setting* · a row every ${HEADLINE_EVERY} turns of a thread, plus the first and deepest; read and write are medians of the 50 turns before it, KB at that turn. Delta should keep both flat as the thread deepens.`
|
|
431
|
+
);
|
|
432
|
+
lines.push(...headlineTable(cells, changesByCell));
|
|
433
|
+
if (!anyBytes(cells)) {
|
|
434
|
+
lines.push('_KB reads `-`: the deployment runs an API without `GET /threads/{id}/storage` yet._');
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
lines.push(
|
|
438
|
+
[
|
|
439
|
+
'*Pass:* errors < 1% and success > 99% per cell, delta on where expected, cold re-read matches the run',
|
|
440
|
+
`▲ better ▼ worse than the last ${MIN_HISTORY_NIGHTS}+ nights`,
|
|
441
|
+
`🟢 quiet · 🟡 a correctness check failed, ${ERROR_ATTENTION}+ failed requests or a regression · 🔴 a cell failed`,
|
|
442
|
+
].join(' · ')
|
|
443
|
+
);
|
|
444
|
+
lines.push(`*Thread:* per-cell detail, the snapshot-frequency sweep and how to read${artifactUrl ? ` · <${artifactUrl}|Artifacts:> summaries and raw samples` : ''}`);
|
|
445
|
+
|
|
446
|
+
return { text: lines.join('\n'), thread: [perCellDetail(cells, changesByCell, history.length), howToRead(cells)] };
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function perCellDetail(cells, changesByCell, nights) {
|
|
450
|
+
const withLoad = cells.find((c) => Number(c.load?.loadSize) > 0);
|
|
451
|
+
const threads = withLoad ? Number(withLoad.load.loadSize) : null;
|
|
452
|
+
const lines = ['*Per cell* · tonight, then against the past nights at the deepest turn every night reached'];
|
|
453
|
+
for (const cell of cells) {
|
|
454
|
+
const m = cell.summary?.metrics;
|
|
455
|
+
if (!m) {
|
|
456
|
+
lines.push(`${cellName(cell)}: no results, step finished as \`${cell.stepOutcome}\``);
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
const top = deepest(cell.buckets);
|
|
460
|
+
const shape = threads && top ? ` (${count(threads)} threads × ${depthLabel(top)} deep)` : '';
|
|
461
|
+
const parts = [`${count(m.failedRuns)} failed of ${count(m.totalRuns)} turns${shape}`];
|
|
462
|
+
const changes = changesByCell.get(cell);
|
|
463
|
+
const sameDepth = new Set(changes.map((c) => c.depth)).size === 1;
|
|
464
|
+
const drift =
|
|
465
|
+
changes.length > 0
|
|
466
|
+
? `vs ${nights} nights${sameDepth ? ` at turn ${depthLabel(changes[0].depth)}` : ''}: ${changes
|
|
467
|
+
.map((c) => `${c.label} ${formatDelta(c.deltaPct)}${sameDepth ? '' : ` (turn ${depthLabel(c.depth)})`}${c.flagged ? ` ${c.arrow}` : ''}`)
|
|
468
|
+
.join(' · ')}`
|
|
469
|
+
: nights < 2
|
|
470
|
+
? 'no history to compare'
|
|
471
|
+
: 'no turn every night reached';
|
|
472
|
+
lines.push(`${cellName(cell)}: ${parts.join(' · ')} · ${drift}`);
|
|
473
|
+
}
|
|
474
|
+
lines.push(
|
|
475
|
+
'',
|
|
476
|
+
'*Snapshot frequency sweep* · delta cells only, columns are the snapshot frequency. Rows: the first turn, the turn before and at each snapshot, and the deepest. Read climbs between snapshots and resets at each one; the full series is in the artifacts.',
|
|
477
|
+
...sweepTables(cells)
|
|
478
|
+
);
|
|
479
|
+
return lines.join('\n');
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/** The longer explanation, at the end of the thread so the channel message stays short. */
|
|
483
|
+
function howToRead(cells) {
|
|
484
|
+
const withLoad = cells.find((c) => Number(c.load?.loadSize) > 0);
|
|
485
|
+
const held = heldFor(withLoad?.load?.plateauDuration);
|
|
486
|
+
return [
|
|
487
|
+
'*How to read*',
|
|
488
|
+
`• *Tests:* ${cells.map((c) => `${cellName(c)} = ${c.label}`).join(' · ')}. Each is one k6 run of the same graph logic; only the channel type and snapshot frequency differ.`,
|
|
489
|
+
withLoad ? `• *Load:* ${count(Number(withLoad.load.loadSize))} users, one thread each${held ? `, held ${held}` : ''}, one turn per iteration, no ramp.` : null,
|
|
490
|
+
'• *Turn:* one `POST /runs/wait` on the thread, which reads the last checkpoint, runs one step and saves a new one. The row label is the thread depth the cold read was taken at.',
|
|
491
|
+
'• *Read:* a cold `GET /threads/{id}/state` every 50 turns. The server rebuilds the value from storage with nothing to write. With delta that is the replay of every diff since the last snapshot.',
|
|
492
|
+
'• *Write:* the turn\'s median latency minus the cold read, so what saving one more checkpoint costs. It still includes the fixed cost of queueing and running the one-node step, identical in every cell, so compare columns; it is not a checkpoint-write latency on its own.',
|
|
493
|
+
'• *Storage:* `GET /threads/{id}/storage` every 50 turns, the bytes the thread occupies at that turn.',
|
|
494
|
+
`• *Regressed / Improved:* read, write or storage at the deepest turn every night reached, outside its own usual range over the last ${MIN_HISTORY_NIGHTS}+ nights.`,
|
|
495
|
+
`• *Errors:* failed requests by HTTP status. ${ERROR_ATTENTION} or more turn the dot yellow even when the cell passes.`,
|
|
496
|
+
'• *Correctness check:* at turn 2 each cell confirms delta is on (delta cells) or off (plain cell), because a too-old langgraph silently degrades a DeltaChannel to a plain one. Every 50 turns the cold re-read is compared to what the run returned. A failure of either is a bug, not a slow night.',
|
|
497
|
+
'• *Rows:* the channel table shows a row every 250 turns plus the first and deepest; the sweep shows the turn before and at each snapshot. Every 50-turn row is in the run artifacts.',
|
|
498
|
+
'• `-` in a cell: no number, the cell produced no results or no thread reached that depth.',
|
|
499
|
+
]
|
|
500
|
+
.filter(Boolean)
|
|
501
|
+
.join('\n');
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function safeBuildReport(cells, context) {
|
|
505
|
+
try {
|
|
506
|
+
return buildReport(cells, context);
|
|
507
|
+
} catch (error) {
|
|
508
|
+
// A bad night still gets reported: say so rather than failing the step.
|
|
509
|
+
return {
|
|
510
|
+
text: [
|
|
511
|
+
'🔴 *Delta Channel*',
|
|
512
|
+
`*Could not build the report:* ${error.message}`,
|
|
513
|
+
`*Step outcomes:* ${cells.map((c) => `${cellName(c)} \`${c.stepOutcome}\``).join(' · ')}`,
|
|
514
|
+
context.artifactUrl ? `<${context.artifactUrl}|Artifacts>` : '',
|
|
515
|
+
]
|
|
516
|
+
.filter(Boolean)
|
|
517
|
+
.join('\n'),
|
|
518
|
+
thread: [],
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
async function main() {
|
|
524
|
+
const cells = await collectCells(process.env.RESULTS_DIR || path.join(HERE, 'results'));
|
|
525
|
+
const outDir = process.env.OUT_DIR || path.join(HERE, 'delta-report');
|
|
526
|
+
mkdirSync(outDir, { recursive: true });
|
|
527
|
+
// Tonight's aggregate, for the "vs last nights" line of the nights to come.
|
|
528
|
+
writeFileSync(
|
|
529
|
+
path.join(outDir, 'delta_report.json'),
|
|
530
|
+
JSON.stringify({ cells: Object.fromEntries(cells.map((c) => [c.cell, { buckets: c.buckets }])) }, null, 2)
|
|
531
|
+
);
|
|
532
|
+
const report = safeBuildReport(cells, {
|
|
533
|
+
apiVersion: process.env.API_VERSION || readApiVersion(),
|
|
534
|
+
artifactUrl: process.env.ARTIFACT_URL,
|
|
535
|
+
manual: process.env.MANUAL === 'true',
|
|
536
|
+
history: process.env.HISTORY_DIR ? loadDeltaHistory(process.env.HISTORY_DIR) : [],
|
|
537
|
+
});
|
|
538
|
+
await postReport(report);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
542
|
+
main().catch((error) => {
|
|
543
|
+
console.error(`Failed to post delta report: ${error.message}`);
|
|
544
|
+
process.exit(1);
|
|
545
|
+
});
|
|
546
|
+
}
|