opencode-a2a 0.4.1__tar.gz → 0.5.1__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.
Files changed (135) hide show
  1. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/.github/workflows/ci.yml +13 -10
  2. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/.github/workflows/dependency-health.yml +3 -5
  3. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/.github/workflows/publish.yml +10 -7
  4. {opencode_a2a-0.4.1/src/opencode_a2a.egg-info → opencode_a2a-0.5.1}/PKG-INFO +20 -6
  5. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/README.md +16 -5
  6. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/docs/guide.md +33 -5
  7. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/pyproject.toml +3 -0
  8. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/scripts/README.md +1 -1
  9. opencode_a2a-0.5.1/scripts/dependency_health.sh +19 -0
  10. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/cli.py +8 -17
  11. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/client/agent_card.py +2 -1
  12. opencode_a2a-0.5.1/src/opencode_a2a/client/auth.py +38 -0
  13. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/client/client.py +16 -5
  14. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/client/config.py +14 -0
  15. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/client/request_context.py +18 -7
  16. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/config.py +20 -3
  17. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/contracts/extensions.py +3 -2
  18. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/execution/executor.py +6 -1
  19. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/execution/session_manager.py +42 -25
  20. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/execution/stream_events.py +1 -1
  21. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/execution/stream_runtime.py +3 -3
  22. opencode_a2a-0.4.1/src/opencode_a2a/execution/upstream_errors.py → opencode_a2a-0.5.1/src/opencode_a2a/execution/upstream_error_translator.py +3 -3
  23. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/jsonrpc/application.py +5 -6
  24. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/opencode_upstream_client.py +32 -79
  25. opencode_a2a-0.5.1/src/opencode_a2a/runtime_state.py +20 -0
  26. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/server/application.py +227 -41
  27. opencode_a2a-0.5.1/src/opencode_a2a/server/state_store.py +656 -0
  28. opencode_a2a-0.5.1/src/opencode_a2a/server/task_store.py +210 -0
  29. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1/src/opencode_a2a.egg-info}/PKG-INFO +20 -6
  30. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a.egg-info/SOURCES.txt +12 -4
  31. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a.egg-info/requires.txt +3 -0
  32. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/client/test_agent_card.py +14 -0
  33. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/client/test_client_config.py +17 -0
  34. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/client/test_client_facade.py +55 -0
  35. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/client/test_request_context.py +25 -0
  36. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/config/test_settings.py +15 -0
  37. opencode_a2a-0.5.1/tests/conftest.py +36 -0
  38. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/contracts/test_extension_contract_consistency.py +1 -1
  39. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/execution/test_cancellation.py +22 -2
  40. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/execution/test_metrics.py +2 -2
  41. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/execution/test_multipart_input.py +3 -3
  42. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/execution/test_opencode_agent_session_binding.py +2 -12
  43. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/execution/test_session_ownership.py +161 -15
  44. opencode_a2a-0.4.1/tests/jsonrpc/test_error_mapping.py → opencode_a2a-0.5.1/tests/jsonrpc/test_error_responses.py +1 -1
  45. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/jsonrpc/test_opencode_session_extension_interrupts.py +7 -7
  46. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/server/test_app_behaviors.py +121 -1
  47. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/server/test_cli.py +10 -18
  48. opencode_a2a-0.5.1/tests/server/test_database_app_persistence.py +210 -0
  49. opencode_a2a-0.5.1/tests/server/test_state_store.py +162 -0
  50. opencode_a2a-0.5.1/tests/server/test_task_store_factory.py +294 -0
  51. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/server/test_transport_contract.py +36 -12
  52. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/support/helpers.py +12 -7
  53. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/support/streaming_output.py +3 -3
  54. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/upstream/test_opencode_upstream_client_params.py +21 -21
  55. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/uv.lock +118 -3
  56. opencode_a2a-0.4.1/scripts/dependency_health.sh +0 -13
  57. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/.gitignore +0 -0
  58. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/.pre-commit-config.yaml +0 -0
  59. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/.secrets.baseline +0 -0
  60. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/AGENTS.md +0 -0
  61. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/CONTRIBUTING.md +0 -0
  62. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/LICENSE +0 -0
  63. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/SECURITY.md +0 -0
  64. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/scripts/check_coverage.py +0 -0
  65. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/scripts/doctor.sh +0 -0
  66. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/scripts/health_common.sh +0 -0
  67. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/scripts/lint.sh +0 -0
  68. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/scripts/smoke_test_built_cli.sh +0 -0
  69. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/setup.cfg +0 -0
  70. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/__init__.py +0 -0
  71. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/client/__init__.py +0 -0
  72. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/client/error_mapping.py +0 -0
  73. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/client/errors.py +0 -0
  74. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/client/payload_text.py +0 -0
  75. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/contracts/__init__.py +0 -0
  76. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/execution/__init__.py +0 -0
  77. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/execution/event_helpers.py +0 -0
  78. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/execution/request_context.py +0 -0
  79. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/execution/stream_state.py +0 -0
  80. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/execution/tool_error_mapping.py +0 -0
  81. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/jsonrpc/__init__.py +0 -0
  82. /opencode_a2a-0.4.1/src/opencode_a2a/jsonrpc/error_mapping.py → /opencode_a2a-0.5.1/src/opencode_a2a/jsonrpc/error_responses.py +0 -0
  83. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/jsonrpc/methods.py +0 -0
  84. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/jsonrpc/params.py +0 -0
  85. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/parts/__init__.py +0 -0
  86. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/parts/mapping.py +0 -0
  87. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/parts/text.py +0 -0
  88. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/profile/__init__.py +0 -0
  89. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/profile/runtime.py +0 -0
  90. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/sandbox_policy.py +0 -0
  91. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/server/__init__.py +0 -0
  92. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/server/agent_card.py +0 -0
  93. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/server/openapi.py +0 -0
  94. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/server/request_parsing.py +0 -0
  95. /opencode_a2a-0.4.1/src/opencode_a2a/error_taxonomy.py → /opencode_a2a-0.5.1/src/opencode_a2a/upstream_taxonomy.py +0 -0
  96. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a.egg-info/dependency_links.txt +0 -0
  97. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a.egg-info/entry_points.txt +0 -0
  98. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a.egg-info/top_level.txt +0 -0
  99. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/__init__.py +0 -0
  100. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/client/__init__.py +0 -0
  101. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/client/test_error_mapping.py +0 -0
  102. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/client/test_payload_text.py +0 -0
  103. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/config/__init__.py +0 -0
  104. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/contracts/__init__.py +0 -0
  105. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/execution/__init__.py +0 -0
  106. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/execution/test_agent_errors.py +0 -0
  107. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/execution/test_agent_helpers.py +0 -0
  108. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/execution/test_directory_validation.py +0 -0
  109. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/execution/test_streaming_output_contract_blocks.py +0 -0
  110. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/execution/test_streaming_output_contract_core.py +0 -0
  111. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/execution/test_streaming_output_contract_interrupts.py +0 -0
  112. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/execution/test_streaming_output_contract_logging.py +0 -0
  113. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/jsonrpc/__init__.py +0 -0
  114. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/jsonrpc/test_jsonrpc_methods.py +0 -0
  115. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/jsonrpc/test_jsonrpc_params.py +0 -0
  116. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/jsonrpc/test_jsonrpc_unsupported_method.py +0 -0
  117. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/jsonrpc/test_opencode_session_extension_commands.py +0 -0
  118. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/jsonrpc/test_opencode_session_extension_prompt_async.py +0 -0
  119. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/jsonrpc/test_opencode_session_extension_queries.py +0 -0
  120. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/package/__init__.py +0 -0
  121. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/package/test_version.py +0 -0
  122. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/parts/__init__.py +0 -0
  123. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/parts/test_parts_text.py +0 -0
  124. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/profile/__init__.py +0 -0
  125. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/profile/test_profile_runtime.py +0 -0
  126. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/scripts/__init__.py +0 -0
  127. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/scripts/test_script_health_contract.py +0 -0
  128. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/server/__init__.py +0 -0
  129. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/server/test_a2a_client_manager.py +0 -0
  130. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/server/test_agent_card.py +0 -0
  131. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/server/test_call_context_builder.py +0 -0
  132. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/server/test_cancel_contract.py +0 -0
  133. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/support/__init__.py +0 -0
  134. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/support/session_extensions.py +0 -0
  135. {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/upstream/__init__.py +0 -0
@@ -8,8 +8,6 @@ on:
8
8
 
9
9
  permissions:
10
10
  contents: read
11
- env:
12
- FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
13
11
 
14
12
  jobs:
15
13
  quality-gate:
@@ -17,15 +15,15 @@ jobs:
17
15
 
18
16
  steps:
19
17
  - name: Checkout
20
- uses: actions/checkout@v4
18
+ uses: actions/checkout@v6
21
19
 
22
20
  - name: Setup Python
23
- uses: actions/setup-python@v5
21
+ uses: actions/setup-python@v6
24
22
  with:
25
23
  python-version: "3.13"
26
24
 
27
25
  - name: Setup uv
28
- uses: astral-sh/setup-uv@v4
26
+ uses: astral-sh/setup-uv@v7
29
27
  with:
30
28
  enable-cache: false
31
29
 
@@ -41,8 +39,13 @@ jobs:
41
39
  - name: Enforce coverage policy
42
40
  run: uv run python ./scripts/check_coverage.py
43
41
 
44
- - name: Run dependency vulnerability audit
45
- run: uv run pip-audit
42
+ - name: Export runtime requirements for vulnerability audit
43
+ run: >
44
+ uv export --format requirements.txt --no-dev --locked --no-emit-project
45
+ --output-file /tmp/runtime-requirements.txt >/dev/null
46
+
47
+ - name: Run runtime dependency vulnerability audit
48
+ run: uv run pip-audit --requirement /tmp/runtime-requirements.txt
46
49
 
47
50
  - name: Clean previous build artifacts
48
51
  run: rm -rf build dist
@@ -65,15 +68,15 @@ jobs:
65
68
 
66
69
  steps:
67
70
  - name: Checkout
68
- uses: actions/checkout@v4
71
+ uses: actions/checkout@v6
69
72
 
70
73
  - name: Setup Python
71
- uses: actions/setup-python@v5
74
+ uses: actions/setup-python@v6
72
75
  with:
73
76
  python-version: ${{ matrix.python-version }}
74
77
 
75
78
  - name: Setup uv
76
- uses: astral-sh/setup-uv@v4
79
+ uses: astral-sh/setup-uv@v7
77
80
  with:
78
81
  enable-cache: false
79
82
 
@@ -8,8 +8,6 @@ on:
8
8
 
9
9
  permissions:
10
10
  contents: read
11
- env:
12
- FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
13
11
 
14
12
  jobs:
15
13
  dependency-health:
@@ -17,15 +15,15 @@ jobs:
17
15
 
18
16
  steps:
19
17
  - name: Checkout
20
- uses: actions/checkout@v4
18
+ uses: actions/checkout@v6
21
19
 
22
20
  - name: Setup Python
23
- uses: actions/setup-python@v5
21
+ uses: actions/setup-python@v6
24
22
  with:
25
23
  python-version: "3.13"
26
24
 
27
25
  - name: Setup uv
28
- uses: astral-sh/setup-uv@v4
26
+ uses: astral-sh/setup-uv@v7
29
27
  with:
30
28
  enable-cache: false
31
29
 
@@ -9,8 +9,6 @@ on:
9
9
  permissions:
10
10
  contents: write
11
11
  id-token: write
12
- env:
13
- FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
14
12
 
15
13
  jobs:
16
14
  publish:
@@ -18,25 +16,30 @@ jobs:
18
16
 
19
17
  steps:
20
18
  - name: Checkout
21
- uses: actions/checkout@v4
19
+ uses: actions/checkout@v6
22
20
  with:
23
21
  fetch-depth: 0
24
22
 
25
23
  - name: Setup Python
26
- uses: actions/setup-python@v5
24
+ uses: actions/setup-python@v6
27
25
  with:
28
26
  python-version: "3.13"
29
27
 
30
28
  - name: Setup uv
31
- uses: astral-sh/setup-uv@v4
29
+ uses: astral-sh/setup-uv@v7
32
30
  with:
33
31
  enable-cache: false
34
32
 
35
33
  - name: Run regression baseline
36
34
  run: bash ./scripts/doctor.sh
37
35
 
38
- - name: Run dependency vulnerability audit
39
- run: uv run pip-audit
36
+ - name: Export runtime requirements for vulnerability audit
37
+ run: >
38
+ uv export --format requirements.txt --no-dev --locked --no-emit-project
39
+ --output-file /tmp/runtime-requirements.txt >/dev/null
40
+
41
+ - name: Run runtime dependency vulnerability audit
42
+ run: uv run pip-audit --requirement /tmp/runtime-requirements.txt
40
43
 
41
44
  - name: Clean previous build artifacts
42
45
  run: rm -rf build dist
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opencode-a2a
3
- Version: 0.4.1
3
+ Version: 0.5.1
4
4
  Summary: OpenCode A2A runtime
5
5
  Author: liujuanjuan1984@Intelligent-Internet
6
6
  License-Expression: Apache-2.0
@@ -20,10 +20,13 @@ Requires-Python: >=3.11
20
20
  Description-Content-Type: text/markdown
21
21
  License-File: LICENSE
22
22
  Requires-Dist: a2a-sdk==0.3.25
23
+ Requires-Dist: aiosqlite<1.0,>=0.20
23
24
  Requires-Dist: fastapi<1.0,>=0.110
24
25
  Requires-Dist: httpx<1.0,>=0.27
25
26
  Requires-Dist: pydantic<3.0,>=2.6
26
27
  Requires-Dist: pydantic-settings<3.0,>=2.2
28
+ Requires-Dist: requests<3.0,>=2.33.0
29
+ Requires-Dist: sqlalchemy<3.0,>=2.0
27
30
  Requires-Dist: sse-starlette<4.0,>=2.1
28
31
  Requires-Dist: uvicorn<1.0,>=0.29
29
32
  Provides-Extra: dev
@@ -144,19 +147,30 @@ curl http://127.0.0.1:8000/.well-known/agent-card.json
144
147
  Interact with other A2A agents directly from the command line:
145
148
 
146
149
  ```bash
147
- opencode-a2a call http://other-agent:8000 "How are you?" --token your-outbound-token
150
+ # Using the target peer agent's Bearer token via environment injection
151
+ A2A_CLIENT_BEARER_TOKEN=your-outbound-token \
152
+ opencode-a2a call http://other-agent:8000 "How are you?"
153
+
154
+ # Using the target peer agent's Basic auth via environment injection
155
+ # Accepts raw user:pass or its base64-encoded value
156
+ A2A_CLIENT_BASIC_AUTH="user:pass" \
157
+ opencode-a2a call http://other-agent:8000 "How are you?"
148
158
  ```
149
159
 
150
160
  ### Outbound Agent Calls (Tools)
151
161
  The server can autonomously execute `a2a_call(url, message)` tool calls emitted by the OpenCode runtime. Results are fetched via A2A and returned to the model as tool results, enabling multi-agent orchestration.
152
162
 
153
- When the target peer requires bearer auth, configure `A2A_CLIENT_BEARER_TOKEN`
154
- for server-side outbound calls. CLI calls can continue using `--token` or
155
- `A2A_CLIENT_BEARER_TOKEN`.
163
+ When the target peer agent requires bearer auth, configure
164
+ `A2A_CLIENT_BEARER_TOKEN` for server-side outbound calls. When the target peer
165
+ agent requires Basic auth, use `A2A_CLIENT_BASIC_AUTH`.
166
+ These outbound credentials apply to the peer specified by `opencode-a2a call`
167
+ or `a2a_call(url, message)`, not to this service's inbound `A2A_BEARER_TOKEN`.
168
+ The CLI intentionally reads outbound credentials from environment variables only,
169
+ so secrets do not appear in shell history or process arguments.
156
170
 
157
171
  Server-side outbound client settings are fully wired through runtime config:
158
172
  `A2A_CLIENT_TIMEOUT_SECONDS`, `A2A_CLIENT_CARD_FETCH_TIMEOUT_SECONDS`,
159
- `A2A_CLIENT_USE_CLIENT_PREFERENCE`, `A2A_CLIENT_BEARER_TOKEN`, and
173
+ `A2A_CLIENT_USE_CLIENT_PREFERENCE`, `A2A_CLIENT_BEARER_TOKEN`, `A2A_CLIENT_BASIC_AUTH`, and
160
174
  `A2A_CLIENT_SUPPORTED_TRANSPORTS`.
161
175
 
162
176
  Detailed protocol contracts, examples, and extension docs live in
@@ -106,19 +106,30 @@ curl http://127.0.0.1:8000/.well-known/agent-card.json
106
106
  Interact with other A2A agents directly from the command line:
107
107
 
108
108
  ```bash
109
- opencode-a2a call http://other-agent:8000 "How are you?" --token your-outbound-token
109
+ # Using the target peer agent's Bearer token via environment injection
110
+ A2A_CLIENT_BEARER_TOKEN=your-outbound-token \
111
+ opencode-a2a call http://other-agent:8000 "How are you?"
112
+
113
+ # Using the target peer agent's Basic auth via environment injection
114
+ # Accepts raw user:pass or its base64-encoded value
115
+ A2A_CLIENT_BASIC_AUTH="user:pass" \
116
+ opencode-a2a call http://other-agent:8000 "How are you?"
110
117
  ```
111
118
 
112
119
  ### Outbound Agent Calls (Tools)
113
120
  The server can autonomously execute `a2a_call(url, message)` tool calls emitted by the OpenCode runtime. Results are fetched via A2A and returned to the model as tool results, enabling multi-agent orchestration.
114
121
 
115
- When the target peer requires bearer auth, configure `A2A_CLIENT_BEARER_TOKEN`
116
- for server-side outbound calls. CLI calls can continue using `--token` or
117
- `A2A_CLIENT_BEARER_TOKEN`.
122
+ When the target peer agent requires bearer auth, configure
123
+ `A2A_CLIENT_BEARER_TOKEN` for server-side outbound calls. When the target peer
124
+ agent requires Basic auth, use `A2A_CLIENT_BASIC_AUTH`.
125
+ These outbound credentials apply to the peer specified by `opencode-a2a call`
126
+ or `a2a_call(url, message)`, not to this service's inbound `A2A_BEARER_TOKEN`.
127
+ The CLI intentionally reads outbound credentials from environment variables only,
128
+ so secrets do not appear in shell history or process arguments.
118
129
 
119
130
  Server-side outbound client settings are fully wired through runtime config:
120
131
  `A2A_CLIENT_TIMEOUT_SECONDS`, `A2A_CLIENT_CARD_FETCH_TIMEOUT_SECONDS`,
121
- `A2A_CLIENT_USE_CLIENT_PREFERENCE`, `A2A_CLIENT_BEARER_TOKEN`, and
132
+ `A2A_CLIENT_USE_CLIENT_PREFERENCE`, `A2A_CLIENT_BEARER_TOKEN`, `A2A_CLIENT_BASIC_AUTH`, and
122
133
  `A2A_CLIENT_SUPPORTED_TRANSPORTS`.
123
134
 
124
135
  Detailed protocol contracts, examples, and extension docs live in
@@ -55,10 +55,10 @@ Key variables to understand protocol behavior:
55
55
  also logged with preview truncation controlled by `A2A_LOG_BODY_LIMIT`.
56
56
  - `A2A_MAX_REQUEST_BODY_BYTES`: runtime request-body limit. Oversized requests
57
57
  return HTTP `413`.
58
- - `A2A_SESSION_CACHE_TTL_SECONDS` / `A2A_SESSION_CACHE_MAXSIZE`: session cache
59
- behavior for `(identity, contextId) -> session_id`.
58
+ - `A2A_PENDING_SESSION_CLAIM_TTL_SECONDS`: lease duration for pending preferred
59
+ session claims before they expire and stop blocking other identities.
60
60
  - `A2A_INTERRUPT_REQUEST_TTL_SECONDS`: active retention window for the
61
- in-memory interrupt request binding cache used by `a2a.interrupt.*`
61
+ interrupt request binding registry used by `a2a.interrupt.*`
62
62
  callback methods. Default: `10800` seconds (`180` minutes).
63
63
  - `A2A_INTERRUPT_REQUEST_TOMBSTONE_TTL_SECONDS`: retention window for expired
64
64
  interrupt tombstones after active TTL has elapsed. During this window,
@@ -76,6 +76,10 @@ Key variables to understand protocol behavior:
76
76
  - `A2A_CLIENT_BEARER_TOKEN`: optional bearer token attached to outbound peer
77
77
  calls made by the embedded A2A client and `a2a_call` tool path.
78
78
  - `A2A_CLIENT_SUPPORTED_TRANSPORTS`: ordered outbound transport preference list.
79
+ - `A2A_TASK_STORE_BACKEND`: runtime state backend. Supported values: `database`,
80
+ `memory`. Default: `database`.
81
+ - `A2A_TASK_STORE_DATABASE_URL`: database URL used by the default durable
82
+ backend. Default: `sqlite+aiosqlite:///./opencode-a2a.db`.
79
83
  - Runtime authentication is bearer-token only via `A2A_BEARER_TOKEN`.
80
84
  - The same outbound client flags are also honored by the server-side embedded
81
85
  A2A client used for peer calls and `a2a_call` tool execution:
@@ -157,6 +161,30 @@ OPENCODE_WORKSPACE_ROOT=/abs/path/to/workspace \
157
161
  opencode-a2a
158
162
  ```
159
163
 
164
+ By default, the service uses a SQLite-backed durable state store:
165
+
166
+ ```bash
167
+ OPENCODE_BASE_URL=http://127.0.0.1:4096 \
168
+ A2A_BEARER_TOKEN=dev-token \
169
+ A2A_TASK_STORE_DATABASE_URL=sqlite+aiosqlite:///./opencode-a2a.db \
170
+ opencode-a2a
171
+ ```
172
+
173
+ With the default `database` backend, the service persists:
174
+
175
+ - task records
176
+ - session binding / ownership state
177
+ - interrupt request bindings and tombstones
178
+
179
+ In-flight asyncio locks, outbound A2A client caches, and stream-local
180
+ aggregation buffers remain process-local runtime state.
181
+
182
+ To opt into an ephemeral development profile, set:
183
+
184
+ ```bash
185
+ A2A_TASK_STORE_BACKEND=memory
186
+ ```
187
+
160
188
  ## Troubleshooting Provider Auth State
161
189
 
162
190
  If one deployment works while another fails against the same upstream provider,
@@ -825,8 +853,8 @@ Notes:
825
853
 
826
854
  - `request_id` must be a live interrupt request observed from stream metadata
827
855
  (`metadata.shared.interrupt.request_id`).
828
- - The server keeps an in-memory interrupt binding cache; callbacks with unknown
829
- or expired `request_id` are rejected.
856
+ - The server keeps an interrupt binding registry; callbacks with unknown or
857
+ expired `request_id` are rejected.
830
858
  - The cache retention windows are controlled by
831
859
  `A2A_INTERRUPT_REQUEST_TTL_SECONDS` (default: `10800` seconds / `180`
832
860
  minutes) and `A2A_INTERRUPT_REQUEST_TOMBSTONE_TTL_SECONDS` (default: `600`
@@ -25,10 +25,13 @@ classifiers = [
25
25
  ]
26
26
  dependencies = [
27
27
  "a2a-sdk==0.3.25",
28
+ "aiosqlite>=0.20,<1.0",
28
29
  "fastapi>=0.110,<1.0",
29
30
  "httpx>=0.27,<1.0",
30
31
  "pydantic>=2.6,<3.0",
31
32
  "pydantic-settings>=2.2,<3.0",
33
+ "requests>=2.33.0,<3.0",
34
+ "sqlalchemy>=2.0,<3.0",
32
35
  "sse-starlette>=2.1,<4.0",
33
36
  "uvicorn>=0.29,<1.0",
34
37
  ]
@@ -13,7 +13,7 @@ remaining repository-maintenance helpers.
13
13
  ## Other Scripts
14
14
 
15
15
  - [`doctor.sh`](./doctor.sh): primary local development regression entrypoint (uv sync + lint + tests + coverage)
16
- - [`dependency_health.sh`](./dependency_health.sh): dependency review entrypoint (`sync`/`pip check` + outdated + audit)
16
+ - [`dependency_health.sh`](./dependency_health.sh): development dependency review entrypoint (`sync`/`pip check` + outdated + dev audit), while blocking CI/publish audits focus on runtime dependencies
17
17
  - [`check_coverage.py`](./check_coverage.py): enforces the overall coverage floor and per-file minimums for critical modules
18
18
  - [`lint.sh`](./lint.sh): lint helper
19
19
  - [`smoke_test_built_cli.sh`](./smoke_test_built_cli.sh): built-artifact smoke test for the released CLI runtime; defaults to the only local wheel, supports explicit wheel/sdist paths, and rejects ambiguous local artifact selection
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # shellcheck source=./health_common.sh
5
+ source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/health_common.sh"
6
+
7
+ run_shared_repo_health_prerequisites "dependency-health"
8
+
9
+ echo "[dependency-health] list outdated packages"
10
+ uv pip list --outdated
11
+
12
+ dev_requirements="$(mktemp)"
13
+ trap 'rm -f "${dev_requirements}"' EXIT
14
+
15
+ echo "[dependency-health] export dev extra requirements"
16
+ uv export --format requirements.txt --extra dev --no-dev --locked --no-emit-project --output-file "${dev_requirements}" >/dev/null
17
+
18
+ echo "[dependency-health] run dev dependency vulnerability audit"
19
+ uv run pip-audit --requirement "${dev_requirements}"
@@ -6,23 +6,19 @@ import os
6
6
  import sys
7
7
  from collections.abc import Sequence
8
8
 
9
+ from a2a.types import Message, TaskArtifactUpdateEvent, TaskStatusUpdateEvent
10
+
9
11
  from . import __version__
12
+ from .client import A2AClient, load_settings
10
13
  from .server.application import main as serve_main
11
14
 
12
15
 
13
- async def run_call(agent_url: str, text: str, token: str | None = None) -> int:
14
- from a2a.types import Message, TaskArtifactUpdateEvent, TaskStatusUpdateEvent
15
-
16
- from .client import A2AClient
17
-
18
- client = A2AClient(agent_url)
19
- metadata = {}
20
- if token:
21
- # Use Authorization header for bearer auth.
22
- metadata["authorization"] = f"Bearer {token}"
16
+ async def run_call(agent_url: str, text: str) -> int:
17
+ settings = load_settings(os.environ)
18
+ client = A2AClient(agent_url, settings=settings)
23
19
 
24
20
  try:
25
- async for event in client.send_message(text, metadata=metadata):
21
+ async for event in client.send_message(text):
26
22
  if isinstance(event, tuple):
27
23
  _, update = event
28
24
  if isinstance(update, TaskArtifactUpdateEvent):
@@ -73,11 +69,6 @@ def build_parser() -> argparse.ArgumentParser:
73
69
  )
74
70
  call_parser.add_argument("agent_url", help="URL of the agent to call.")
75
71
  call_parser.add_argument("text", help="Text message to send.")
76
- call_parser.add_argument(
77
- "--token",
78
- help="Bearer token for authentication (can also use A2A_CLIENT_BEARER_TOKEN env).",
79
- default=os.environ.get("A2A_CLIENT_BEARER_TOKEN"),
80
- )
81
72
 
82
73
  return parser
83
74
 
@@ -92,7 +83,7 @@ def main(argv: Sequence[str] | None = None) -> int:
92
83
 
93
84
  namespace = parser.parse_args(args)
94
85
  if namespace.command == "call":
95
- return asyncio.run(run_call(namespace.agent_url, namespace.text, namespace.token))
86
+ return asyncio.run(run_call(namespace.agent_url, namespace.text))
96
87
 
97
88
  if namespace.command is None:
98
89
  serve_main()
@@ -66,9 +66,10 @@ def build_resolver_http_kwargs(
66
66
  *,
67
67
  bearer_token: str | None,
68
68
  timeout: float,
69
+ basic_auth: str | None = None,
69
70
  ) -> dict[str, Any]:
70
71
  http_kwargs: dict[str, Any] = {"timeout": timeout}
71
- default_headers = build_default_headers(bearer_token)
72
+ default_headers = build_default_headers(bearer_token, basic_auth)
72
73
  if default_headers:
73
74
  http_kwargs["headers"] = default_headers
74
75
  return http_kwargs
@@ -0,0 +1,38 @@
1
+ """Authentication helpers for outbound A2A client configuration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from base64 import b64decode, b64encode
6
+ from binascii import Error as BinasciiError
7
+
8
+ BASIC_AUTH_FORMAT_ERROR = (
9
+ "A2A_CLIENT_BASIC_AUTH must be 'username:password' or a base64-encoded "
10
+ "'username:password' value"
11
+ )
12
+
13
+
14
+ def validate_basic_auth(value: str) -> None:
15
+ """Validate a raw or pre-encoded Basic Auth credential value."""
16
+ if ":" in value:
17
+ return
18
+ decoded = _decode_basic_auth(value)
19
+ if b":" not in decoded:
20
+ raise ValueError(BASIC_AUTH_FORMAT_ERROR)
21
+
22
+
23
+ def encode_basic_auth(value: str) -> str:
24
+ """Normalize a raw or pre-encoded Basic Auth value to base64 credentials."""
25
+ if ":" in value:
26
+ return b64encode(value.encode()).decode()
27
+ decoded = _decode_basic_auth(value)
28
+ if b":" not in decoded:
29
+ raise ValueError(BASIC_AUTH_FORMAT_ERROR)
30
+ return b64encode(decoded).decode()
31
+
32
+
33
+ def _decode_basic_auth(value: str) -> bytes:
34
+ padded_value = value + ("=" * (-len(value) % 4))
35
+ try:
36
+ return b64decode(padded_value, validate=True)
37
+ except (BinasciiError, ValueError) as exc:
38
+ raise ValueError(BASIC_AUTH_FORMAT_ERROR) from exc
@@ -83,6 +83,7 @@ class A2AClient:
83
83
  http_kwargs=build_resolver_http_kwargs(
84
84
  bearer_token=self._settings.bearer_token,
85
85
  timeout=self._settings.card_fetch_timeout,
86
+ basic_auth=self._settings.basic_auth,
86
87
  )
87
88
  )
88
89
  except (
@@ -121,7 +122,9 @@ class A2AClient:
121
122
  try:
122
123
  async for event in client.send_message(
123
124
  request,
124
- context=build_call_context(self._settings.bearer_token, extra_headers),
125
+ context=build_call_context(
126
+ self._settings.bearer_token, extra_headers, self._settings.basic_auth
127
+ ),
125
128
  request_metadata=request_metadata,
126
129
  extensions=extensions,
127
130
  ):
@@ -180,7 +183,9 @@ class A2AClient:
180
183
  history_length=history_length,
181
184
  metadata=request_metadata or {},
182
185
  ),
183
- context=build_call_context(self._settings.bearer_token, extra_headers),
186
+ context=build_call_context(
187
+ self._settings.bearer_token, extra_headers, self._settings.basic_auth
188
+ ),
184
189
  )
185
190
  except (
186
191
  A2AClientHTTPError,
@@ -206,7 +211,9 @@ class A2AClient:
206
211
  try:
207
212
  return await client.cancel_task(
208
213
  TaskIdParams(id=task_id, metadata=request_metadata or {}),
209
- context=build_call_context(self._settings.bearer_token, extra_headers),
214
+ context=build_call_context(
215
+ self._settings.bearer_token, extra_headers, self._settings.basic_auth
216
+ ),
210
217
  )
211
218
  except (
212
219
  A2AClientHTTPError,
@@ -232,7 +239,9 @@ class A2AClient:
232
239
  try:
233
240
  async for event in client.resubscribe(
234
241
  TaskIdParams(id=task_id, metadata=request_metadata or {}),
235
- context=build_call_context(self._settings.bearer_token, extra_headers),
242
+ context=build_call_context(
243
+ self._settings.bearer_token, extra_headers, self._settings.basic_auth
244
+ ),
236
245
  ):
237
246
  yield event
238
247
  except (
@@ -264,7 +273,9 @@ class A2AClient:
264
273
  factory = ClientFactory(config, consumers=None)
265
274
  client = factory.create(
266
275
  card,
267
- interceptors=build_client_interceptors(self._settings.bearer_token),
276
+ interceptors=build_client_interceptors(
277
+ self._settings.bearer_token, self._settings.basic_auth
278
+ ),
268
279
  )
269
280
  except ValueError as exc:
270
281
  raise A2AUnsupportedBindingError(
@@ -6,6 +6,8 @@ from collections.abc import Iterable, Mapping
6
6
  from dataclasses import dataclass
7
7
  from typing import Any
8
8
 
9
+ from .auth import validate_basic_auth
10
+
9
11
 
10
12
  def _read_setting(
11
13
  source: Any,
@@ -106,6 +108,7 @@ class A2AClientSettings:
106
108
  use_client_preference: bool = False
107
109
  card_fetch_timeout: float = 5.0
108
110
  bearer_token: str | None = None
111
+ basic_auth: str | None = None
109
112
  supported_transports: tuple[str, ...] = (
110
113
  "JSONRPC",
111
114
  "HTTP+JSON",
@@ -153,6 +156,16 @@ def load_settings(raw_settings: Any) -> A2AClientSettings:
153
156
  default=None,
154
157
  ),
155
158
  )
159
+ basic_auth = _coerce_optional_str(
160
+ "A2A_CLIENT_BASIC_AUTH",
161
+ _read_setting(
162
+ raw_settings,
163
+ keys=("A2A_CLIENT_BASIC_AUTH", "a2a_client_basic_auth"),
164
+ default=None,
165
+ ),
166
+ )
167
+ if basic_auth is not None:
168
+ validate_basic_auth(basic_auth)
156
169
  supported_transports = _parse_transports(
157
170
  _read_setting(
158
171
  raw_settings,
@@ -170,6 +183,7 @@ def load_settings(raw_settings: Any) -> A2AClientSettings:
170
183
  use_client_preference=use_client_preference,
171
184
  card_fetch_timeout=card_fetch_timeout,
172
185
  bearer_token=bearer_token,
186
+ basic_auth=basic_auth,
173
187
  supported_transports=supported_transports,
174
188
  )
175
189
 
@@ -7,6 +7,8 @@ from typing import Any
7
7
 
8
8
  from a2a.client.middleware import ClientCallContext, ClientCallInterceptor
9
9
 
10
+ from .auth import encode_basic_auth
11
+
10
12
 
11
13
  class HeaderInterceptor(ClientCallInterceptor):
12
14
  def __init__(self, default_headers: Mapping[str, str] | None = None) -> None:
@@ -36,10 +38,15 @@ class HeaderInterceptor(ClientCallInterceptor):
36
38
  return request_payload, http_kwargs
37
39
 
38
40
 
39
- def build_default_headers(bearer_token: str | None) -> dict[str, str]:
40
- if not bearer_token:
41
- return {}
42
- return {"Authorization": f"Bearer {bearer_token}"}
41
+ def build_default_headers(
42
+ bearer_token: str | None,
43
+ basic_auth: str | None = None,
44
+ ) -> dict[str, str]:
45
+ if bearer_token:
46
+ return {"Authorization": f"Bearer {bearer_token}"}
47
+ if basic_auth:
48
+ return {"Authorization": f"Basic {encode_basic_auth(basic_auth)}"}
49
+ return {}
43
50
 
44
51
 
45
52
  def split_request_metadata(
@@ -59,8 +66,9 @@ def split_request_metadata(
59
66
  def build_call_context(
60
67
  bearer_token: str | None,
61
68
  extra_headers: Mapping[str, str] | None,
69
+ basic_auth: str | None = None,
62
70
  ) -> ClientCallContext | None:
63
- merged_headers = build_default_headers(bearer_token)
71
+ merged_headers = build_default_headers(bearer_token, basic_auth)
64
72
  if extra_headers:
65
73
  merged_headers.update(extra_headers)
66
74
  if not merged_headers:
@@ -73,8 +81,11 @@ def build_call_context(
73
81
  )
74
82
 
75
83
 
76
- def build_client_interceptors(bearer_token: str | None) -> list[ClientCallInterceptor]:
77
- return [HeaderInterceptor(build_default_headers(bearer_token))]
84
+ def build_client_interceptors(
85
+ bearer_token: str | None,
86
+ basic_auth: str | None = None,
87
+ ) -> list[ClientCallInterceptor]:
88
+ return [HeaderInterceptor(build_default_headers(bearer_token, basic_auth))]
78
89
 
79
90
 
80
91
  __all__ = [
@@ -35,6 +35,7 @@ WriteAccessScope = Literal[
35
35
  "custom",
36
36
  ]
37
37
  OutsideWorkspaceAccess = Literal["unknown", "allowed", "disallowed", "custom"]
38
+ TaskStoreBackend = Literal["memory", "database"]
38
39
 
39
40
 
40
41
  def _parse_declared_list(value: Any) -> tuple[str, ...]:
@@ -132,9 +133,11 @@ class Settings(BaseSettings):
132
133
  a2a_port: int = Field(default=8000, alias="A2A_PORT")
133
134
  a2a_bearer_token: str = Field(..., min_length=1, alias="A2A_BEARER_TOKEN")
134
135
 
135
- # Session cache settings
136
- a2a_session_cache_ttl_seconds: int = Field(default=3600, alias="A2A_SESSION_CACHE_TTL_SECONDS")
137
- a2a_session_cache_maxsize: int = Field(default=10_000, alias="A2A_SESSION_CACHE_MAXSIZE")
136
+ a2a_pending_session_claim_ttl_seconds: float = Field(
137
+ default=30.0,
138
+ gt=0.0,
139
+ alias="A2A_PENDING_SESSION_CLAIM_TTL_SECONDS",
140
+ )
138
141
  a2a_interrupt_request_ttl_seconds: float = Field(
139
142
  default=10_800.0,
140
143
  ge=0.0,
@@ -176,7 +179,21 @@ class Settings(BaseSettings):
176
179
  alias="A2A_CLIENT_SUPPORTED_TRANSPORTS",
177
180
  )
178
181
 
182
+ # Task store settings
183
+ a2a_task_store_backend: TaskStoreBackend = Field(
184
+ default="database",
185
+ alias="A2A_TASK_STORE_BACKEND",
186
+ )
187
+ a2a_task_store_database_url: str | None = Field(
188
+ default="sqlite+aiosqlite:///./opencode-a2a.db",
189
+ alias="A2A_TASK_STORE_DATABASE_URL",
190
+ )
191
+
179
192
  @model_validator(mode="after")
180
193
  def _validate_sandbox_policy(self) -> Settings:
181
194
  SandboxPolicy.from_settings(self).validate_configuration()
195
+ if self.a2a_task_store_backend == "database" and not self.a2a_task_store_database_url:
196
+ raise ValueError(
197
+ "A2A_TASK_STORE_DATABASE_URL is required when A2A_TASK_STORE_BACKEND=database"
198
+ )
182
199
  return self
@@ -450,8 +450,9 @@ def build_session_binding_extension_params(
450
450
  "message to that upstream session."
451
451
  ),
452
452
  (
453
- "Otherwise, the server will create a new upstream session and cache "
454
- "the (identity, contextId)->session_id mapping in memory with TTL."
453
+ "Otherwise, the server will create a new upstream session and retain "
454
+ "the (identity, contextId)->session_id mapping according to the "
455
+ "configured task/state store backend and TTL policy."
455
456
  ),
456
457
  ],
457
458
  }