ms-tau-sdk 1.2.9.dev15__tar.gz → 1.2.9.dev17__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.
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/CHANGELOG.md +15 -2
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/PKG-INFO +26 -2
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/README.md +25 -1
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/packages/tau-board/src/ms_tau_board/app.py +5 -1
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/packages/tau-board/src/ms_tau_board/state.py +174 -7
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/packages/tau-board/src/ms_tau_board/static/app.js +87 -4
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/packages/tau-board/src/ms_tau_board/static/board.css +4 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/packages/tau-board/src/ms_tau_board/static/index.html +12 -3
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/pyproject.toml +1 -1
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/agent_skills/tau_a2a_runtime_adapter/SKILL.md +20 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/agent_skills/tau_local_development/SKILL.md +36 -1
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/agent_skills/tau_project_customization/SKILL.md +5 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/agent_skills/tau_repository_integration/SKILL.md +39 -3
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/api/a2a.py +491 -84
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/app.py +20 -8
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/backend/client.py +41 -6
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/backend/local.py +827 -51
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/backend/models.py +17 -2
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/backend/routes.py +1 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/errors.py +7 -0
- ms_tau_sdk-1.2.9.dev17/src/ms_tau_sdk/protocols/a2a_failure.py +118 -0
- ms_tau_sdk-1.2.9.dev17/src/ms_tau_sdk/protocols/a2a_roles.py +78 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/runtime/manager.py +30 -2
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/runtime/task_context.py +14 -1
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/settings.py +28 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/tools/task_control.py +4 -4
- ms_tau_sdk-1.2.9.dev15/src/ms_tau_sdk/protocols/a2a_roles.py +0 -23
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/.gitignore +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/packages/tau-board/src/ms_tau_board/__init__.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/packages/tau-board/src/ms_tau_board/cli.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/packages/tau-board/src/ms_tau_board/config.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/packages/tau-board/src/ms_tau_board/env_file.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/packages/tau-board/src/ms_tau_board/logs.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/packages/tau-board/src/ms_tau_board/proxy.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/packages/tau-board/src/ms_tau_board/static/BULMA-LICENSE.txt +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/packages/tau-board/src/ms_tau_board/static/bulma.min.css +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/__init__.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/api/__init__.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/api/chat.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/api/dependencies.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/api/health.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/api/inspection.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/api/models.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/api/sessions.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/application.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/backend/__init__.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/backend/auth.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/backend/mcp.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/cli.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/logging.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/protocols/__init__.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/protocols/a2a_message.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/protocols/assistant_ui.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/protocols/strict_json.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/providers/__init__.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/providers/definitions.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/providers/factory.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/resources/SYSTEM.md +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/resources/__init__.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/resources/loader.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/resources/prompts/review-code-repository.md +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/runtime/__init__.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/runtime/deployment_health.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/runtime/events.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/runtime/extensions.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/runtime/failures.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/runtime/observability.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/runtime/provenance.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/runtime/session.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/runtime/snapshots.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/sessions/__init__.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/sessions/storage.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/skills.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/tools/__init__.py +0 -0
- {ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/src/ms_tau_sdk/tools/mainsequence_mcp.py +0 -0
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 1.2.9 — 2026-09-
|
|
4
|
-
|
|
3
|
+
## 1.2.9 — 2026-09-26
|
|
4
|
+
|
|
5
|
+
- Added bounded A2A Task Message history with hard-cut A2A v1 role projection, canonical durable
|
|
6
|
+
status Messages, exact idempotent settlement replay, and reference-only status events.
|
|
7
|
+
- Correlated every Task attempt with one immutable Tau turn and exact entry boundaries, including
|
|
8
|
+
local Task-owned lease fencing, committed/abandoned resolution, and late-write rejection.
|
|
9
|
+
- Rebuilt Tau Board Task detail around Overview, Conversation, Result, Execution, and Technical
|
|
10
|
+
evidence, with byte-exact streamed Artifact reconstruction and bounded secret-redacted execution
|
|
11
|
+
entries.
|
|
12
|
+
- Guaranteed A2A failure observability: standard terminal failed status events, conformant safe
|
|
13
|
+
status Messages, explicit terminalization-unknown transport errors, and bounded request waiting.
|
|
14
|
+
- Added SDK-owned local Task reconciliation. Unclaimed submitted Tasks resume after restart;
|
|
15
|
+
exhausted starts fail terminally, while stale working attempts with uncertain side effects fail
|
|
16
|
+
as ambiguous instead of being blindly executed twice. Health, structured logs, and Tau Board now
|
|
17
|
+
expose recovery and failure evidence.
|
|
5
18
|
- Added Tau Board's Agent view for readable effective Agent Card inspection, source-grouped tool
|
|
6
19
|
catalog inspection, project extension diagnostics, and bounded registered-entry source viewing.
|
|
7
20
|
- Added an explicit local project-tool workbench with schema-generated inputs, authoritative
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ms-tau-sdk
|
|
3
|
-
Version: 1.2.9.
|
|
3
|
+
Version: 1.2.9.dev17
|
|
4
4
|
Summary: Workspace-bound Tau application primitives for Main Sequence projects
|
|
5
5
|
Project-URL: Changelog, https://github.com/mainsequence-sdk/ms-tau-sdk/blob/development/CHANGELOG.md
|
|
6
6
|
Project-URL: Documentation, https://github.com/mainsequence-sdk/ms-tau-sdk/tree/development/docs
|
|
@@ -116,6 +116,12 @@ from ms_tau_sdk import create_app
|
|
|
116
116
|
app = create_app()
|
|
117
117
|
```
|
|
118
118
|
|
|
119
|
+
Job-hosted batch execution is an [accepted design](docs/adrs/0015-job-hosted-batch-execution.md)
|
|
120
|
+
with implementation pending. It will run this SDK's configured Tau composition for one assignment
|
|
121
|
+
inside a project Job, close it, and exit without starting Uvicorn. The Job continues to own its
|
|
122
|
+
status and logs. See the [batch guide](docs/guides/job-hosted-batch-execution.md) for the target
|
|
123
|
+
Python integration; the current release does not expose that batch API.
|
|
124
|
+
|
|
119
125
|
## Local development without platform sessions
|
|
120
126
|
|
|
121
127
|
Local mode runs the same workspace Tau runtime without registering an Agent or AgentSession. The
|
|
@@ -144,7 +150,25 @@ workspace default. Public A2A Message, Task, streaming, continuation, list/get/c
|
|
|
144
150
|
subscription flows run without creating a platform AgentSession. Incoming local A2A calls do not
|
|
145
151
|
need managed-gateway caller headers: supplied context IDs are mapped into the workspace-local
|
|
146
152
|
session namespace and local provenance is recorded. Task records and event streams survive process
|
|
147
|
-
restart.
|
|
153
|
+
restart. On startup TAU reschedules unclaimed `submitted` Tasks. A stale `working` Task is not
|
|
154
|
+
blindly executed again: if the previous process may have produced an external side effect, TAU
|
|
155
|
+
settles it as `failed` with `ambiguous_execution_outcome`.
|
|
156
|
+
|
|
157
|
+
Task terminality comes only from the standard A2A state. `completed`, `failed`, `canceled`, and
|
|
158
|
+
`rejected` are terminal; `input_required` and `auth_required` are resumable interruptions. A normal
|
|
159
|
+
provider, tool, validation, or execution failure is durably settled as `failed` with a safe A2A
|
|
160
|
+
status Message. If TAU cannot persist that transition, a live stream reports
|
|
161
|
+
`task_terminalization_unknown`; poll or subscribe to the Task while its recovery owner resolves the
|
|
162
|
+
durable state. `returnImmediately=false` waits for at most the configured Task wait period and does
|
|
163
|
+
not cancel a still-running Task when that transport wait expires.
|
|
164
|
+
|
|
165
|
+
Task responses expose bounded A2A Message history separately from Artifacts and execution data.
|
|
166
|
+
Use `configuration.historyLength` on Message send/stream and `historyLength` on Task get/list;
|
|
167
|
+
omission selects the SDK's bounded 100-Message tail, zero omits history without reading it, and a
|
|
168
|
+
positive value returns that latest tail oldest-to-newest. Public A2A roles are `ROLE_USER` and
|
|
169
|
+
`ROLE_AGENT`; Main Sequence requester/responder direction values remain internal to persistence.
|
|
170
|
+
Tau Board renders the conversation, final Artifacts, exact attempt/turn execution, and raw technical
|
|
171
|
+
events as distinct sections.
|
|
148
172
|
|
|
149
173
|
Local mode also appends privacy-filtered, structured JSON Lines to
|
|
150
174
|
`~/.tau/mainsequence/<workspace-hash>/logs/tau.jsonl`, with bounded rotation. Both the database
|
|
@@ -85,6 +85,12 @@ from ms_tau_sdk import create_app
|
|
|
85
85
|
app = create_app()
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
+
Job-hosted batch execution is an [accepted design](docs/adrs/0015-job-hosted-batch-execution.md)
|
|
89
|
+
with implementation pending. It will run this SDK's configured Tau composition for one assignment
|
|
90
|
+
inside a project Job, close it, and exit without starting Uvicorn. The Job continues to own its
|
|
91
|
+
status and logs. See the [batch guide](docs/guides/job-hosted-batch-execution.md) for the target
|
|
92
|
+
Python integration; the current release does not expose that batch API.
|
|
93
|
+
|
|
88
94
|
## Local development without platform sessions
|
|
89
95
|
|
|
90
96
|
Local mode runs the same workspace Tau runtime without registering an Agent or AgentSession. The
|
|
@@ -113,7 +119,25 @@ workspace default. Public A2A Message, Task, streaming, continuation, list/get/c
|
|
|
113
119
|
subscription flows run without creating a platform AgentSession. Incoming local A2A calls do not
|
|
114
120
|
need managed-gateway caller headers: supplied context IDs are mapped into the workspace-local
|
|
115
121
|
session namespace and local provenance is recorded. Task records and event streams survive process
|
|
116
|
-
restart.
|
|
122
|
+
restart. On startup TAU reschedules unclaimed `submitted` Tasks. A stale `working` Task is not
|
|
123
|
+
blindly executed again: if the previous process may have produced an external side effect, TAU
|
|
124
|
+
settles it as `failed` with `ambiguous_execution_outcome`.
|
|
125
|
+
|
|
126
|
+
Task terminality comes only from the standard A2A state. `completed`, `failed`, `canceled`, and
|
|
127
|
+
`rejected` are terminal; `input_required` and `auth_required` are resumable interruptions. A normal
|
|
128
|
+
provider, tool, validation, or execution failure is durably settled as `failed` with a safe A2A
|
|
129
|
+
status Message. If TAU cannot persist that transition, a live stream reports
|
|
130
|
+
`task_terminalization_unknown`; poll or subscribe to the Task while its recovery owner resolves the
|
|
131
|
+
durable state. `returnImmediately=false` waits for at most the configured Task wait period and does
|
|
132
|
+
not cancel a still-running Task when that transport wait expires.
|
|
133
|
+
|
|
134
|
+
Task responses expose bounded A2A Message history separately from Artifacts and execution data.
|
|
135
|
+
Use `configuration.historyLength` on Message send/stream and `historyLength` on Task get/list;
|
|
136
|
+
omission selects the SDK's bounded 100-Message tail, zero omits history without reading it, and a
|
|
137
|
+
positive value returns that latest tail oldest-to-newest. Public A2A roles are `ROLE_USER` and
|
|
138
|
+
`ROLE_AGENT`; Main Sequence requester/responder direction values remain internal to persistence.
|
|
139
|
+
Tau Board renders the conversation, final Artifacts, exact attempt/turn execution, and raw technical
|
|
140
|
+
events as distinct sections.
|
|
117
141
|
|
|
118
142
|
Local mode also appends privacy-filtered, structured JSON Lines to
|
|
119
143
|
`~/.tau/mainsequence/<workspace-hash>/logs/tau.jsonl`, with bounded rotation. Both the database
|
{ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/packages/tau-board/src/ms_tau_board/app.py
RENAMED
|
@@ -283,10 +283,14 @@ async def session_list(request: Request) -> Response:
|
|
|
283
283
|
|
|
284
284
|
async def task_list(request: Request) -> Response:
|
|
285
285
|
token, session, new = _session(request)
|
|
286
|
-
|
|
286
|
+
profile = _selected(request, session)
|
|
287
|
+
directory = _directory(request, session, profile)
|
|
287
288
|
result = await asyncio.to_thread(
|
|
288
289
|
state.tasks, directory, session_uid=request.query_params.get("sessionUid", "")
|
|
289
290
|
)
|
|
291
|
+
health = session.health.get(profile.name, {})
|
|
292
|
+
policy = health.get("a2a_task_policy")
|
|
293
|
+
result["policy"] = policy if isinstance(policy, dict) else {}
|
|
290
294
|
return _cookie(JSONResponse(result), token, new)
|
|
291
295
|
|
|
292
296
|
|
{ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/packages/tau-board/src/ms_tau_board/state.py
RENAMED
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import json
|
|
6
|
+
import re
|
|
5
7
|
import sqlite3
|
|
6
8
|
from contextlib import closing
|
|
7
9
|
from pathlib import Path
|
|
8
10
|
from typing import Any
|
|
9
11
|
|
|
10
|
-
SCHEMA_VERSION = "
|
|
12
|
+
SCHEMA_VERSION = "4"
|
|
11
13
|
TABLES = (
|
|
12
14
|
"sessions",
|
|
13
15
|
"entries",
|
|
@@ -27,6 +29,20 @@ PRIVATE_FIELDS = {
|
|
|
27
29
|
"status_message_json",
|
|
28
30
|
"metadata_json",
|
|
29
31
|
}
|
|
32
|
+
EXECUTION_VALUE_MAX_BYTES = 64 * 1024
|
|
33
|
+
_SENSITIVE_ENTRY_KEYS = {
|
|
34
|
+
"api_key",
|
|
35
|
+
"authorization",
|
|
36
|
+
"client_secret",
|
|
37
|
+
"credential",
|
|
38
|
+
"credentials",
|
|
39
|
+
"password",
|
|
40
|
+
"prompt",
|
|
41
|
+
"secret",
|
|
42
|
+
"system_prompt",
|
|
43
|
+
"token",
|
|
44
|
+
}
|
|
45
|
+
_BEARER_PATTERN = re.compile(r"(?i)\bbearer\s+[A-Za-z0-9._~+/=-]+")
|
|
30
46
|
|
|
31
47
|
|
|
32
48
|
class StateError(ValueError):
|
|
@@ -167,7 +183,9 @@ def tasks(directory: Path, *, session_uid: str = "", limit: int = 100) -> dict[s
|
|
|
167
183
|
params: tuple[object, ...] = (session_uid, limit) if session_uid else (limit,)
|
|
168
184
|
rows = connection.execute(
|
|
169
185
|
"SELECT uid, task_id, context_id, agent_session_uid, status, "
|
|
170
|
-
"status_timestamp, cancellation_requested,
|
|
186
|
+
"status_timestamp, cancellation_requested, failure_code, "
|
|
187
|
+
"failure_category, failure_retryable, correlation_id, recovery_owner, "
|
|
188
|
+
"recovery_count, terminal_at, created_at, updated_at "
|
|
171
189
|
f"FROM a2a_tasks {where} ORDER BY created_at DESC LIMIT ?",
|
|
172
190
|
params,
|
|
173
191
|
).fetchall()
|
|
@@ -190,6 +208,15 @@ def task_detail(directory: Path, task_id: str) -> dict[str, Any]:
|
|
|
190
208
|
if task is None:
|
|
191
209
|
raise StateError("Task not found")
|
|
192
210
|
uid = task["uid"]
|
|
211
|
+
runtime_selection = connection.execute(
|
|
212
|
+
"SELECT provider, model, thinking FROM sessions WHERE uid = ?",
|
|
213
|
+
(task["context_id"],),
|
|
214
|
+
).fetchone()
|
|
215
|
+
messages = connection.execute(
|
|
216
|
+
"SELECT sequence, message_id, role, message_json, created_at "
|
|
217
|
+
"FROM a2a_task_messages WHERE task_uid = ? ORDER BY sequence",
|
|
218
|
+
(uid,),
|
|
219
|
+
).fetchall()
|
|
193
220
|
events = connection.execute(
|
|
194
221
|
"SELECT sequence, uid, event_type, status, message_uid, output_uid, "
|
|
195
222
|
"payload_json, created_at FROM a2a_task_events "
|
|
@@ -197,20 +224,160 @@ def task_detail(directory: Path, task_id: str) -> dict[str, Any]:
|
|
|
197
224
|
(uid,),
|
|
198
225
|
).fetchall()
|
|
199
226
|
attempts = connection.execute(
|
|
200
|
-
"SELECT uid, attempt_number, state,
|
|
227
|
+
"SELECT uid, attempt_number, state, outcome_category, failure_code, "
|
|
228
|
+
"failure_detail, retryable, correlation_id, turn_uid, "
|
|
229
|
+
"entry_start_sequence, entry_end_sequence, turn_resolution, "
|
|
230
|
+
"created_at, updated_at "
|
|
201
231
|
"FROM a2a_task_attempts WHERE task_uid = ? ORDER BY attempt_number",
|
|
202
232
|
(uid,),
|
|
203
233
|
).fetchall()
|
|
204
234
|
outputs = connection.execute(
|
|
205
|
-
"SELECT artifact_id, revision, name,
|
|
235
|
+
"SELECT artifact_id, revision, name, parts_json, metadata_json, "
|
|
236
|
+
"finalized, created_at, updated_at "
|
|
206
237
|
"FROM a2a_task_outputs WHERE task_uid = ? ORDER BY created_at",
|
|
207
238
|
(uid,),
|
|
208
239
|
).fetchall()
|
|
240
|
+
execution_entries = connection.execute(
|
|
241
|
+
"""
|
|
242
|
+
SELECT DISTINCT entries.sequence, entries.entry_type,
|
|
243
|
+
entries.entry_json, entries.idempotency_key, entries.turn_uid
|
|
244
|
+
FROM entries
|
|
245
|
+
JOIN a2a_task_attempts AS attempts
|
|
246
|
+
ON attempts.turn_uid = entries.turn_uid
|
|
247
|
+
AND attempts.task_uid = ?
|
|
248
|
+
WHERE entries.session_uid = ?
|
|
249
|
+
AND attempts.turn_uid IS NOT NULL
|
|
250
|
+
AND (
|
|
251
|
+
attempts.entry_start_sequence IS NULL
|
|
252
|
+
OR entries.sequence >= attempts.entry_start_sequence
|
|
253
|
+
)
|
|
254
|
+
AND (
|
|
255
|
+
attempts.entry_end_sequence IS NULL
|
|
256
|
+
OR entries.sequence < attempts.entry_end_sequence
|
|
257
|
+
)
|
|
258
|
+
ORDER BY entries.sequence
|
|
259
|
+
""",
|
|
260
|
+
(uid, task["context_id"]),
|
|
261
|
+
).fetchall()
|
|
209
262
|
except sqlite3.Error as error:
|
|
210
263
|
raise StateError(f"Cannot read local Task: {type(error).__name__}") from error
|
|
264
|
+
task_value = dict(task)
|
|
265
|
+
raw_status_message = task_value.pop("status_message_json", None)
|
|
266
|
+
try:
|
|
267
|
+
status_message = json.loads(raw_status_message) if raw_status_message else None
|
|
268
|
+
except (TypeError, json.JSONDecodeError):
|
|
269
|
+
status_message = None
|
|
270
|
+
task_value["status_message"] = status_message
|
|
271
|
+
task_value["runtime"] = dict(runtime_selection) if runtime_selection is not None else None
|
|
272
|
+
status_text = ""
|
|
273
|
+
if isinstance(status_message, dict):
|
|
274
|
+
parts = status_message.get("parts")
|
|
275
|
+
if isinstance(parts, list):
|
|
276
|
+
status_text = "\n".join(
|
|
277
|
+
str(part.get("text") or "")
|
|
278
|
+
for part in parts
|
|
279
|
+
if isinstance(part, dict) and part.get("text")
|
|
280
|
+
)
|
|
281
|
+
task_value["failure"] = {
|
|
282
|
+
"code": str(task_value.get("failure_code") or ""),
|
|
283
|
+
"category": str(task_value.get("failure_category") or ""),
|
|
284
|
+
"retryable": (
|
|
285
|
+
bool(task_value["failure_retryable"])
|
|
286
|
+
if task_value.get("failure_retryable") is not None
|
|
287
|
+
else None
|
|
288
|
+
),
|
|
289
|
+
"message": status_text,
|
|
290
|
+
"correlationId": str(task_value.get("correlation_id") or ""),
|
|
291
|
+
}
|
|
292
|
+
message_values = []
|
|
293
|
+
for row in messages:
|
|
294
|
+
value = dict(row)
|
|
295
|
+
value["message"] = _decode_json(value.pop("message_json"), {})
|
|
296
|
+
message_values.append(value)
|
|
297
|
+
output_values = []
|
|
298
|
+
for row in outputs:
|
|
299
|
+
value = dict(row)
|
|
300
|
+
parts = _decode_json(value.pop("parts_json"), [])
|
|
301
|
+
metadata = _decode_json(value.pop("metadata_json"), {})
|
|
302
|
+
text = "".join(str(part.get("text") or "") for part in parts if isinstance(part, dict))
|
|
303
|
+
byte_size = 0
|
|
304
|
+
for part in parts:
|
|
305
|
+
if not isinstance(part, dict):
|
|
306
|
+
continue
|
|
307
|
+
if isinstance(part.get("text"), str):
|
|
308
|
+
byte_size += len(part["text"].encode("utf-8"))
|
|
309
|
+
elif "data" in part:
|
|
310
|
+
byte_size += len(
|
|
311
|
+
json.dumps(
|
|
312
|
+
part["data"],
|
|
313
|
+
ensure_ascii=False,
|
|
314
|
+
separators=(",", ":"),
|
|
315
|
+
sort_keys=True,
|
|
316
|
+
).encode("utf-8")
|
|
317
|
+
)
|
|
318
|
+
value.update(
|
|
319
|
+
{
|
|
320
|
+
"parts": parts,
|
|
321
|
+
"metadata": metadata,
|
|
322
|
+
"text": text,
|
|
323
|
+
"byte_size": byte_size,
|
|
324
|
+
}
|
|
325
|
+
)
|
|
326
|
+
output_values.append(value)
|
|
327
|
+
event_values = []
|
|
328
|
+
for row in events:
|
|
329
|
+
value = dict(row)
|
|
330
|
+
value["payload"] = _decode_json(value.pop("payload_json"), {})
|
|
331
|
+
event_values.append(value)
|
|
332
|
+
entry_values = []
|
|
333
|
+
for row in execution_entries:
|
|
334
|
+
value = dict(row)
|
|
335
|
+
value["entry"] = _safe_execution_value(_decode_json(value.pop("entry_json"), {}))
|
|
336
|
+
entry_values.append(value)
|
|
211
337
|
return {
|
|
212
|
-
"task":
|
|
213
|
-
"
|
|
338
|
+
"task": task_value,
|
|
339
|
+
"messages": message_values,
|
|
340
|
+
"events": event_values,
|
|
214
341
|
"attempts": [dict(row) for row in attempts],
|
|
215
|
-
"outputs":
|
|
342
|
+
"outputs": output_values,
|
|
343
|
+
"executionEntries": entry_values,
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def _decode_json(value: object, default: Any) -> Any:
|
|
348
|
+
if value is None or value == "":
|
|
349
|
+
return default
|
|
350
|
+
try:
|
|
351
|
+
return json.loads(str(value))
|
|
352
|
+
except (TypeError, json.JSONDecodeError):
|
|
353
|
+
return default
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def _safe_execution_value(value: Any, *, key: str = "") -> Any:
|
|
357
|
+
"""Bound and redact operational entry data before it reaches the browser."""
|
|
358
|
+
|
|
359
|
+
normalized = key.lower().replace("-", "_")
|
|
360
|
+
if normalized in _SENSITIVE_ENTRY_KEYS or normalized.endswith(
|
|
361
|
+
("_credential", "_password", "_secret", "_token")
|
|
362
|
+
):
|
|
363
|
+
return "[REDACTED]"
|
|
364
|
+
safe: Any
|
|
365
|
+
if isinstance(value, dict):
|
|
366
|
+
safe = {
|
|
367
|
+
str(child_key): _safe_execution_value(child, key=str(child_key))
|
|
368
|
+
for child_key, child in value.items()
|
|
369
|
+
}
|
|
370
|
+
elif isinstance(value, list):
|
|
371
|
+
safe = [_safe_execution_value(child) for child in value]
|
|
372
|
+
elif isinstance(value, str):
|
|
373
|
+
safe = _BEARER_PATTERN.sub("Bearer [REDACTED]", value)
|
|
374
|
+
else:
|
|
375
|
+
safe = value
|
|
376
|
+
encoded = json.dumps(safe, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
|
|
377
|
+
if len(encoded) <= EXECUTION_VALUE_MAX_BYTES:
|
|
378
|
+
return safe
|
|
379
|
+
return {
|
|
380
|
+
"truncated": True,
|
|
381
|
+
"originalBytes": len(encoded),
|
|
382
|
+
"message": "Execution entry exceeds the Tau Board inspection limit.",
|
|
216
383
|
}
|
{ms_tau_sdk-1.2.9.dev15 → ms_tau_sdk-1.2.9.dev17}/packages/tau-board/src/ms_tau_board/static/app.js
RENAMED
|
@@ -539,7 +539,8 @@ function displayTask(task) {
|
|
|
539
539
|
$("a2a-context").value = task.contextId || "";
|
|
540
540
|
const timing = taskTiming(task);
|
|
541
541
|
const statusLabel = timing.completed ? "Completed" : (timing.terminal ? "Terminal status" : "Status recorded");
|
|
542
|
-
|
|
542
|
+
const statusText = (task.status?.message?.parts || []).map((part) => part.text || "").filter(Boolean).join("\n");
|
|
543
|
+
$("task-output").textContent = `${task.id} · ${task.status?.state || "unknown"}\nCreated: ${displayTime(timing.created)}\n${statusLabel}: ${displayTime(timing.statusTime)}${statusText ? `\nReason: ${statusText}` : ""}\n\n${taskText(task)}`;
|
|
543
544
|
}
|
|
544
545
|
function handleA2AFrame(frame) {
|
|
545
546
|
const value = frame.result || frame;
|
|
@@ -713,7 +714,12 @@ async function refreshTaskExplorer() {
|
|
|
713
714
|
const result = await json(`/api/board/tasks?${query}`, { headers });
|
|
714
715
|
const list = $("tasks-list"); clear(list);
|
|
715
716
|
for (const task of result.tasks || []) {
|
|
716
|
-
const
|
|
717
|
+
const threshold = task.status === "submitted" ? result.policy?.pending_timeout_seconds : result.policy?.stale_after_seconds;
|
|
718
|
+
const recorded = task.status === "submitted" ? task.created_at : task.status_timestamp;
|
|
719
|
+
const ageSeconds = recorded ? (Date.now() - new Date(recorded).valueOf()) / 1000 : 0;
|
|
720
|
+
const stale = ["submitted", "working"].includes(task.status) && threshold && ageSeconds > threshold;
|
|
721
|
+
const suffix = task.failure_code ? `\n${task.failure_code}` : (stale ? "\nRecovery overdue" : "");
|
|
722
|
+
const button = node("button", `${task.task_id} · ${task.status}\n${task.context_id}${suffix}`, stale ? "button is-warning" : "button is-light");
|
|
717
723
|
button.type = "button";
|
|
718
724
|
button.classList.toggle("is-selected", selectedTaskId === task.task_id);
|
|
719
725
|
button.addEventListener("click", () => inspectTask(task.task_id).catch(report));
|
|
@@ -765,12 +771,89 @@ async function loadTaskLogs(reset = false) {
|
|
|
765
771
|
renderTaskTimeline();
|
|
766
772
|
}
|
|
767
773
|
|
|
774
|
+
function renderTaskConversation() {
|
|
775
|
+
const list = $("tasks-conversation"); clear(list);
|
|
776
|
+
for (const record of selectedTaskData?.messages || []) {
|
|
777
|
+
const message = record.message || {};
|
|
778
|
+
const card = node("article", "", "task-card");
|
|
779
|
+
const head = node("div", "", "task-card-head");
|
|
780
|
+
const role = message.role === "ROLE_REQUESTER" ? "Requester" : (message.role === "ROLE_RESPONDER" ? "Responder" : (message.role || "Message"));
|
|
781
|
+
head.append(node("strong", role), node("span", `#${record.sequence}`, "tag is-light"), node("span", displayTime(record.created_at), "mono"));
|
|
782
|
+
card.append(head);
|
|
783
|
+
for (const part of message.parts || []) {
|
|
784
|
+
if (typeof part.text === "string") card.append(node("pre", part.text));
|
|
785
|
+
else if (Object.hasOwn(part, "data")) card.append(node("pre", JSON.stringify(part.data, null, 2)));
|
|
786
|
+
else card.append(node("pre", JSON.stringify(part, null, 2)));
|
|
787
|
+
}
|
|
788
|
+
list.append(card);
|
|
789
|
+
}
|
|
790
|
+
if (!(selectedTaskData?.messages || []).length) list.append(node("p", "No durable Task Messages.", "help"));
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
function renderTaskResults() {
|
|
794
|
+
const list = $("tasks-result"); clear(list);
|
|
795
|
+
for (const artifact of selectedTaskData?.outputs || []) {
|
|
796
|
+
const card = node("article", "", "task-card");
|
|
797
|
+
const head = node("div", "", "task-card-head");
|
|
798
|
+
head.append(node("strong", artifact.name || artifact.artifact_id), node("span", `revision ${artifact.revision}`, "tag is-light"), node("span", artifact.finalized ? "Final" : "Open", artifact.finalized ? "tag is-success is-light" : "tag is-warning is-light"), node("span", `${artifact.byte_size || 0} bytes`, "tag is-light"));
|
|
799
|
+
card.append(head);
|
|
800
|
+
if (artifact.text) card.append(node("pre", artifact.text));
|
|
801
|
+
for (const part of artifact.parts || []) {
|
|
802
|
+
if (!Object.hasOwn(part, "data")) continue;
|
|
803
|
+
card.append(node("pre", JSON.stringify(part.data, null, 2)));
|
|
804
|
+
}
|
|
805
|
+
list.append(card);
|
|
806
|
+
}
|
|
807
|
+
if (!(selectedTaskData?.outputs || []).length) list.append(node("p", "No Task Artifacts.", "help"));
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
function executionLabel(entry) {
|
|
811
|
+
const raw = JSON.stringify(entry.entry || {}).toLowerCase();
|
|
812
|
+
if (raw.includes("tool_result") || raw.includes("toolresult")) return "Tool result";
|
|
813
|
+
if (raw.includes("tool_call") || raw.includes("toolcall")) return "Tool call";
|
|
814
|
+
if (raw.includes("reasoning") || raw.includes("thinking")) return "Recorded reasoning";
|
|
815
|
+
if (entry.entry_type === "message") return "Model message";
|
|
816
|
+
return entry.entry_type || "Tau entry";
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
function executionOutcome(entry) {
|
|
820
|
+
const value = entry.entry || {};
|
|
821
|
+
const raw = JSON.stringify(value).toLowerCase();
|
|
822
|
+
if (raw.includes('"is_error":true') || raw.includes('"error":true') || raw.includes('"status":"failed"')) return "Failed";
|
|
823
|
+
if (raw.includes('"status":"completed"') || raw.includes('"status":"success"') || raw.includes('"success":true')) return "Succeeded";
|
|
824
|
+
return "";
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
function renderTaskExecution() {
|
|
828
|
+
const list = $("tasks-execution"); clear(list);
|
|
829
|
+
const entries = selectedTaskData?.executionEntries || [];
|
|
830
|
+
for (const attempt of selectedTaskData?.attempts || []) {
|
|
831
|
+
const card = node("article", "", "task-card");
|
|
832
|
+
const head = node("div", "", "task-card-head");
|
|
833
|
+
head.append(node("strong", `Attempt ${attempt.attempt_number}`), node("span", attempt.state, "tag is-info is-light"), node("span", attempt.turn_resolution || "unresolved", attempt.turn_resolution === "committed" ? "tag is-success is-light" : "tag is-warning is-light"));
|
|
834
|
+
card.append(head);
|
|
835
|
+
detailLine(card, "Turn", attempt.turn_uid || "—");
|
|
836
|
+
detailLine(card, "Entry interval", attempt.entry_start_sequence == null ? "—" : `[${attempt.entry_start_sequence}, ${attempt.entry_end_sequence ?? "?"})`);
|
|
837
|
+
const attemptEntries = entries.filter((entry) => entry.turn_uid && entry.turn_uid === attempt.turn_uid && (attempt.entry_start_sequence == null || entry.sequence >= attempt.entry_start_sequence) && (attempt.entry_end_sequence == null || entry.sequence < attempt.entry_end_sequence));
|
|
838
|
+
for (const entry of attemptEntries) card.append(taskEventItem("Tau", `#${entry.sequence}`, executionLabel(entry), entry, executionOutcome(entry)));
|
|
839
|
+
if (!attemptEntries.length) card.append(node("p", "No retained entries in this turn interval.", "help"));
|
|
840
|
+
list.append(card);
|
|
841
|
+
}
|
|
842
|
+
if (!(selectedTaskData?.attempts || []).length) list.append(node("p", "No execution attempts.", "help"));
|
|
843
|
+
}
|
|
844
|
+
|
|
768
845
|
async function inspectTask(taskId) {
|
|
769
846
|
selectedTaskId = taskId;
|
|
770
847
|
selectedTaskData = await json(`/api/board/tasks/${encodeURIComponent(taskId)}`, { headers: { "X-Tau-Board-Profile": $("tasks-profile").value } });
|
|
771
848
|
const task = selectedTaskData.task;
|
|
772
849
|
const detail = $("tasks-detail"); clear(detail);
|
|
773
|
-
|
|
850
|
+
const failure = task.failure || {};
|
|
851
|
+
const runtime = task.runtime || {};
|
|
852
|
+
const duration = task.created_at && (task.terminal_at || task.status_timestamp) ? Math.max(0, new Date(task.terminal_at || task.status_timestamp).valueOf() - new Date(task.created_at).valueOf()) : null;
|
|
853
|
+
for (const [label, value] of [["Task ID", task.task_id], ["Session", task.context_id], ["Status", task.status], ["Created", displayTime(task.created_at)], ["Status time", displayTime(task.status_timestamp)], ["Terminal time", displayTime(task.terminal_at)], ["Duration", duration == null || Number.isNaN(duration) ? "—" : `${duration} ms`], ["Provider", runtime.provider || "—"], ["Model", runtime.model || "—"], ["Thinking", runtime.thinking || "—"], ["Failure", failure.message || "—"], ["Failure code", failure.code || "—"], ["Failure category", failure.category || "—"], ["Retry safe", failure.retryable == null ? "—" : String(failure.retryable)], ["Recovery owner", task.recovery_owner || "—"], ["Recovery attempts", task.recovery_count || 0], ["Correlation ID", failure.correlationId || "—"], ["Attempts", selectedTaskData.attempts.length], ["Messages", selectedTaskData.messages.length], ["Artifacts", selectedTaskData.outputs.length]]) detailLine(detail, label, value);
|
|
854
|
+
renderTaskConversation();
|
|
855
|
+
renderTaskResults();
|
|
856
|
+
renderTaskExecution();
|
|
774
857
|
$("tasks-open-a2a").disabled = false;
|
|
775
858
|
$("tasks-open-session").disabled = false;
|
|
776
859
|
for (const button of $("tasks-list").querySelectorAll("button")) button.classList.toggle("is-selected", button.textContent.startsWith(`${taskId} ·`));
|
|
@@ -928,7 +1011,7 @@ async function start() {
|
|
|
928
1011
|
bind("a2a-new-task", "click", () => { $("a2a-task-id").value = ""; $("a2a-kind").value = "task"; $("a2a-input").focus(); });
|
|
929
1012
|
bind("refresh-tasks", "click", refreshTasks);
|
|
930
1013
|
bind("tasks-refresh", "click", refreshTaskExplorer);
|
|
931
|
-
bind("tasks-profile", "change", () => { selectedTaskId = ""; selectedTaskData = null;
|
|
1014
|
+
bind("tasks-profile", "change", () => { selectedTaskId = ""; selectedTaskData = null; for (const id of ["tasks-detail", "tasks-conversation", "tasks-result", "tasks-execution", "tasks-timeline"]) clear($(id)); return refreshTaskExplorer(); });
|
|
932
1015
|
bind("tasks-session", "change", refreshTaskExplorer);
|
|
933
1016
|
bind("tasks-open-id", "click", () => inspectTask($("tasks-id").value.trim()));
|
|
934
1017
|
bind("tasks-older", "click", () => loadTaskLogs());
|
|
@@ -40,6 +40,10 @@ td { max-width: 24rem; overflow-wrap: anywhere; font-size: .82rem; }
|
|
|
40
40
|
.tool-field { margin-bottom: .8rem; }
|
|
41
41
|
.extension-card { border-bottom: 1px solid var(--bulma-border-weak); padding: .55rem 0; font-size: .82rem; overflow-wrap: anywhere; }
|
|
42
42
|
#tasks-detail { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 1rem; }
|
|
43
|
+
.task-section { display: grid; gap: .7rem; }
|
|
44
|
+
.task-card { border: 1px solid var(--bulma-border-weak); border-radius: .45rem; padding: .8rem; background: var(--bulma-scheme-main-bis); }
|
|
45
|
+
.task-card .task-card-head { display: flex; flex-wrap: wrap; gap: .45rem; align-items: center; margin-bottom: .55rem; }
|
|
46
|
+
.task-card pre { margin-top: .5rem; white-space: pre-wrap; overflow-wrap: anywhere; max-height: 24rem; overflow: auto; }
|
|
43
47
|
.settings-table td { vertical-align: middle; }
|
|
44
48
|
.settings-table td:first-child { min-width: 15rem; }
|
|
45
49
|
.settings-table td:last-child { min-width: 14rem; }
|
|
@@ -146,9 +146,18 @@
|
|
|
146
146
|
<div class="column"><div class="box">
|
|
147
147
|
<h2 class="title is-5">Task detail</h2>
|
|
148
148
|
<div id="tasks-detail" class="detail-list">Select a Task to inspect its status and events.</div>
|
|
149
|
-
<div class="buttons mt-4"><button class="button is-small" id="tasks-open-a2a" disabled>Open in A2A</button><button class="button is-small" id="tasks-open-session" disabled>Open session logs</button></div>
|
|
150
|
-
<h3 class="title is-6 mt-5">
|
|
151
|
-
<p class="help">
|
|
149
|
+
<div class="buttons mt-4"><button class="button is-small" id="tasks-open-a2a" disabled>Open in A2A Console</button><button class="button is-small" id="tasks-open-session" disabled>Open session logs</button></div>
|
|
150
|
+
<h3 class="title is-6 mt-5">Conversation</h3>
|
|
151
|
+
<p class="help">Durable requester and responder Messages associated with this Task.</p>
|
|
152
|
+
<div id="tasks-conversation" class="task-section"></div>
|
|
153
|
+
<h3 class="title is-6 mt-5">Result</h3>
|
|
154
|
+
<p class="help">Final Artifacts. Streamed text fragments are joined without added separators.</p>
|
|
155
|
+
<div id="tasks-result" class="task-section"></div>
|
|
156
|
+
<h3 class="title is-6 mt-5">Execution</h3>
|
|
157
|
+
<p class="help">Exact attempt and Tau turn correlation. Internal evidence is operational data, not public Task history.</p>
|
|
158
|
+
<div id="tasks-execution" class="task-section"></div>
|
|
159
|
+
<h3 class="title is-6 mt-5">Technical</h3>
|
|
160
|
+
<p class="help">Task events and related session logs are labelled separately. Expand a row for raw evidence.</p>
|
|
152
161
|
<div id="tasks-timeline" class="log-list"></div>
|
|
153
162
|
<button class="button is-small mt-3 is-hidden" id="tasks-older">Load older logs</button>
|
|
154
163
|
</div></div>
|
|
@@ -76,5 +76,25 @@ When an A2A operation fails, identify the boundary before changing code:
|
|
|
76
76
|
5. Wire validation: request/response role, context, extension, or payload invalid.
|
|
77
77
|
6. Task lifecycle: fence, lease, cancellation, event order, or retry semantics violated.
|
|
78
78
|
|
|
79
|
+
For Task lifecycle diagnosis, derive terminality from the Task state; never introduce a second
|
|
80
|
+
terminal flag. Normal execution errors must settle `failed` and streaming must emit that
|
|
81
|
+
authoritative terminal status update. `task_terminalization_unknown` means settlement durability
|
|
82
|
+
is unknown and belongs to the recovery owner. Local SQLite recovery is SDK-owned; managed dispatch
|
|
83
|
+
recovery is backend-owned. Do not retry a stale working attempt when project or MCP side effects may
|
|
84
|
+
have occurred unless a checkpoint or idempotency contract proves replay safe.
|
|
85
|
+
|
|
86
|
+
Treat Task Messages, Artifacts, Tau entries, Task events, and logs as separate ontologies. Use
|
|
87
|
+
`historyLength` only for the bounded durable Message tail. Omission means the SDK default of 100,
|
|
88
|
+
zero omits history without a tail read, and positive values return the latest bounded tail in
|
|
89
|
+
oldest-to-newest order. Public A2A v1 roles are `ROLE_USER` and `ROLE_AGENT`; persistence uses the
|
|
90
|
+
Main Sequence requester/responder direction values. Never expose internal Tau entries, reasoning,
|
|
91
|
+
tool traffic, prompts, or logs as public Task history.
|
|
92
|
+
|
|
93
|
+
Allocate the Tau turn UID before attempt start and pass that exact UID to the runtime prompt. A
|
|
94
|
+
Task attempt owns one turn and one half-open entry interval with a `committed` or `abandoned`
|
|
95
|
+
resolution. Do not correlate by timestamps. A status settlement is absent or one complete durable
|
|
96
|
+
responder Message; never send the removed ad hoc status-detail shape. Status events reference that
|
|
97
|
+
Message and consumers reload the Task snapshot.
|
|
98
|
+
|
|
79
99
|
Change this SDK only for catalog projection, proof attachment, runtime execution, or transport
|
|
80
100
|
translation defects. Platform contract changes belong to Django.
|
|
@@ -77,7 +77,9 @@ a platform AgentSession. Local mode supports:
|
|
|
77
77
|
- subscription resumption with `afterSequence`;
|
|
78
78
|
- continuation of a Task in `input_required` or `auth_required` by sending a new Message carrying
|
|
79
79
|
that `taskId`; and
|
|
80
|
-
- Task status, messages, artifacts, attempts, and events surviving process restart
|
|
80
|
+
- Task status, messages, artifacts, attempts, and events surviving process restart, with the SDK
|
|
81
|
+
rescheduling safe unclaimed work and terminalizing stale uncertain work rather than replaying
|
|
82
|
+
possible side effects.
|
|
81
83
|
|
|
82
84
|
To request a Task from `message:send`, send the response-kind extension header
|
|
83
85
|
`A2A-Extensions: https://mainsequence.ai/a2a/extensions/response-kind/v1` together with
|
|
@@ -85,6 +87,33 @@ To request a Task from `message:send`, send the response-kind extension header
|
|
|
85
87
|
response. `message:stream` is always Task-oriented and does not accept `responseKind` or
|
|
86
88
|
`returnImmediately`.
|
|
87
89
|
|
|
90
|
+
Task terminality is derived only from its A2A state. `completed`, `failed`, `canceled`, and
|
|
91
|
+
`rejected` are terminal; `input_required` and `auth_required` are resumable. A normal execution
|
|
92
|
+
failure produces a durable failed Task and a standard responder `status.message`. A
|
|
93
|
+
`task_terminalization_unknown` stream error means the terminal write itself could not be proven;
|
|
94
|
+
retrieve or subscribe to the Task while recovery resolves it. Never treat an HTTP disconnect or
|
|
95
|
+
bounded wait expiry as Task cancellation.
|
|
96
|
+
|
|
97
|
+
Task responses include a bounded Message tail by default. Set `configuration.historyLength` on
|
|
98
|
+
Message send/stream or `historyLength` on Task get/list: zero omits history and performs no tail
|
|
99
|
+
read, while a positive value returns the latest Messages oldest-to-newest, capped at 100. History
|
|
100
|
+
contains requester and deliberately durable responder/status Messages only. Final output remains
|
|
101
|
+
under `artifacts`; Tau entries, tool activity, events, and logs never become public history. Public
|
|
102
|
+
roles are `ROLE_USER`/`ROLE_AGENT`; the local SQLite binding stores requester/responder directions.
|
|
103
|
+
|
|
104
|
+
Each local Task attempt reserves one immutable Tau turn. Its entries share that turn UID and remain
|
|
105
|
+
inside the recorded half-open sequence interval. Completed and interrupted settlement requires a
|
|
106
|
+
committed turn; failed, rejected, or canceled settlement can abandon a pending turn, after which
|
|
107
|
+
late append or commit is rejected. An unresolved Task-owned turn prevents lease replacement until
|
|
108
|
+
the local Task reconciler records its recovery outcome. Ordinary chat/direct-Message turns have no
|
|
109
|
+
Task owner and retain normal lease cleanup.
|
|
110
|
+
|
|
111
|
+
After restart, the local reconciler schedules an unclaimed `submitted` Task from its persisted
|
|
112
|
+
request. It does not blindly rerun a stale `working` Task: once the old lease and stale interval
|
|
113
|
+
expire, an attempt without replay-safe checkpoint evidence becomes failed with
|
|
114
|
+
`ambiguous_execution_outcome`. Inspect the failure code, attempt, recovery owner, and correlation ID
|
|
115
|
+
in Tau Board and the structured log.
|
|
116
|
+
|
|
88
117
|
Incoming local A2A requests do not need managed-gateway `X-Caller-*` headers. TAU assigns
|
|
89
118
|
workspace-local provenance and maps each supplied `contextId` into the same workspace-scoped local
|
|
90
119
|
session namespace used by chat. A supplied `taskId` remains the public Task identifier. The local
|
|
@@ -123,6 +152,12 @@ tool still has the Tau process's real filesystem, network, environment, and cred
|
|
|
123
152
|
there is no automatic rollback. A busy session, stale catalog, expired confirmation, non-project
|
|
124
153
|
tool, oversized output, timeout, or cancellation must fail explicitly.
|
|
125
154
|
|
|
155
|
+
The Tasks tab is the primary inspection surface: Overview states the lifecycle, Conversation shows
|
|
156
|
+
durable Messages, Result shows complete Artifacts, Execution uses exact attempt/turn correlation,
|
|
157
|
+
and Technical retains mutation events and logs. **Open in A2A Console** is an action shortcut, not
|
|
158
|
+
the Task explanation. Many `output_updated` records represent revisions of one Artifact and must
|
|
159
|
+
not be interpreted as many agent responses.
|
|
160
|
+
|
|
126
161
|
## Outbound A2A through Main Sequence MCP
|
|
127
162
|
|
|
128
163
|
Keep the projected `a2a.send_message` tool available. A local agent may send a Message or create a
|
|
@@ -59,6 +59,11 @@ Set them in `harness_agent.spec.env_vars` in a managed repository workflow, in t
|
|
|
59
59
|
environment for local `ms-tau`, or through explicit `TauSDKSettings` fields in a Python host. They
|
|
60
60
|
apply to all sessions in that process; neither Agent Card skills nor prompt text removes tools.
|
|
61
61
|
|
|
62
|
+
The accepted, pending job-hosted batch entry point uses these same two settings. It must not
|
|
63
|
+
start Main Sequence MCP or add coding tools when the corresponding source is excluded. The table
|
|
64
|
+
below describes the currently implemented service and local modes; batch behavior for A2A-only
|
|
65
|
+
Task controls requires an actual Task context and is specified by ADR 0015.
|
|
66
|
+
|
|
62
67
|
| Exclude base tools | Exclude Main Sequence MCP | Model-facing sources |
|
|
63
68
|
| --- | --- | --- |
|
|
64
69
|
| `false` | `false` | Coding tools, Main Sequence MCP, project extension tools, A2A Task controls. |
|