ms-tau-sdk 1.2.12.dev20__tar.gz → 1.2.13.dev22__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 (80) hide show
  1. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/CHANGELOG.md +46 -3
  2. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/PKG-INFO +29 -8
  3. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/README.md +28 -7
  4. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/packages/tau-board/src/ms_tau_board/state.py +3 -1
  5. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/packages/tau-board/src/ms_tau_board/static/app.js +1 -0
  6. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/packages/tau-board/src/ms_tau_board/static/index.html +1 -1
  7. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/pyproject.toml +1 -1
  8. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/agent_skills/tau_local_development/SKILL.md +35 -13
  9. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/api/chat.py +20 -3
  10. ms_tau_sdk-1.2.13.dev22/src/ms_tau_sdk/api/local_chat.py +256 -0
  11. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/app.py +6 -1
  12. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/application.py +2 -2
  13. ms_tau_sdk-1.2.13.dev22/src/ms_tau_sdk/backend/auth.py +480 -0
  14. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/backend/client.py +26 -0
  15. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/backend/local.py +279 -12
  16. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/backend/models.py +31 -0
  17. ms_tau_sdk-1.2.13.dev22/src/ms_tau_sdk/protocols/chat_history.py +496 -0
  18. ms_tau_sdk-1.2.13.dev22/src/ms_tau_sdk/runtime/live_turns.py +135 -0
  19. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/runtime/manager.py +174 -4
  20. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/settings.py +122 -9
  21. ms_tau_sdk-1.2.12.dev20/src/ms_tau_sdk/backend/auth.py +0 -212
  22. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/.gitignore +0 -0
  23. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/packages/tau-board/src/ms_tau_board/__init__.py +0 -0
  24. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/packages/tau-board/src/ms_tau_board/app.py +0 -0
  25. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/packages/tau-board/src/ms_tau_board/cli.py +0 -0
  26. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/packages/tau-board/src/ms_tau_board/config.py +0 -0
  27. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/packages/tau-board/src/ms_tau_board/env_file.py +0 -0
  28. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/packages/tau-board/src/ms_tau_board/logs.py +0 -0
  29. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/packages/tau-board/src/ms_tau_board/proxy.py +0 -0
  30. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/packages/tau-board/src/ms_tau_board/static/BULMA-LICENSE.txt +0 -0
  31. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/packages/tau-board/src/ms_tau_board/static/board.css +0 -0
  32. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/packages/tau-board/src/ms_tau_board/static/bulma.min.css +0 -0
  33. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/__init__.py +0 -0
  34. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/agent_skills/tau_a2a_runtime_adapter/SKILL.md +0 -0
  35. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/agent_skills/tau_project_customization/SKILL.md +0 -0
  36. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/agent_skills/tau_repository_integration/SKILL.md +0 -0
  37. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/api/__init__.py +0 -0
  38. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/api/a2a.py +0 -0
  39. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/api/conversations.py +0 -0
  40. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/api/dependencies.py +0 -0
  41. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/api/health.py +0 -0
  42. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/api/inspection.py +0 -0
  43. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/api/models.py +0 -0
  44. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/api/sessions.py +0 -0
  45. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/backend/__init__.py +0 -0
  46. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/backend/mcp.py +0 -0
  47. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/backend/routes.py +0 -0
  48. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/cli.py +0 -0
  49. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/errors.py +0 -0
  50. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/logging.py +0 -0
  51. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/protocols/__init__.py +0 -0
  52. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/protocols/a2a_failure.py +0 -0
  53. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/protocols/a2a_message.py +0 -0
  54. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/protocols/a2a_roles.py +0 -0
  55. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/protocols/assistant_ui.py +0 -0
  56. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/protocols/strict_json.py +0 -0
  57. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/providers/__init__.py +0 -0
  58. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/providers/definitions.py +0 -0
  59. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/providers/factory.py +0 -0
  60. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/providers/tau_compat.py +0 -0
  61. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/resources/SYSTEM.md +0 -0
  62. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/resources/__init__.py +0 -0
  63. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/resources/loader.py +0 -0
  64. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/resources/prompts/review-code-repository.md +0 -0
  65. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/runtime/__init__.py +0 -0
  66. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/runtime/deployment_health.py +0 -0
  67. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/runtime/events.py +0 -0
  68. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/runtime/extensions.py +0 -0
  69. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/runtime/failures.py +0 -0
  70. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/runtime/observability.py +0 -0
  71. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/runtime/provenance.py +0 -0
  72. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/runtime/session.py +0 -0
  73. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/runtime/snapshots.py +0 -0
  74. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/runtime/task_context.py +0 -0
  75. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/sessions/__init__.py +0 -0
  76. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/sessions/storage.py +0 -0
  77. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/skills.py +0 -0
  78. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/tools/__init__.py +0 -0
  79. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/tools/mainsequence_mcp.py +0 -0
  80. {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/tools/task_control.py +0 -0
@@ -2,6 +2,49 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ - Local mode no longer needs a token in the environment or in the project `.env`. With neither
6
+ `MAINSEQUENCE_ACCESS_TOKEN` nor `MAINSEQUENCE_REFRESH_TOKEN` set, it asks a Main Sequence CLI
7
+ for the access token with `mainsequence auth token --json`: log in once with
8
+ `mainsequence login`. The SDK looks for the CLI in the new `MAINSEQUENCE_CLI` setting, then
9
+ beside the Python interpreter, then on `PATH`. It keeps the token in memory, reuses it until 60
10
+ seconds before it expires, and asks again after a rejected request. It refuses an answer for
11
+ another backend. A missing CLI, a CLI too old for the command, a missing or expired session, a
12
+ machine without a credential store, a time-out, and malformed output each have their own error.
13
+ The SDK still does not depend on or import the `mainsequence` package and does not read the
14
+ CLI's credential store. The token pair stays supported in the process environment, for
15
+ launchers and CI, and with both tokens set the CLI is never run. A pair read from the project
16
+ `.env` file still works but is deprecated: startup logs one warning that names the file and the
17
+ two variables, and `mainsequence refresh-token` run in that directory removes them. `/health`
18
+ and `/ready` report the source in `mainsequence_auth_source` (`cli`, `environment`, or
19
+ `env_file`; `null` in managed mode), and Tau Board shows it in Connect. The owner scope of local
20
+ chat sessions and direct A2A conversations is derived from the subject of the CLI token, so a
21
+ user keeps the same local sessions with either source. See the 2026-09-30 amendments of ADR 0005
22
+ and ADR 0017.
23
+ - Tau Board again reads the local state the SDK writes. 1.2.12 raised the local SQLite schema
24
+ version to 6 without raising the version Tau Board accepts, so the board refused every store
25
+ that 1.2.12 had created or opened: the State, session, Task, and Task-log views returned
26
+ "Unknown Tau local SQLite schema version". Schema 6 only adds the `local_chat_sessions` table,
27
+ and the board's queries are unchanged. The board still accepts exactly one schema version, so a
28
+ store last opened by 1.2.11 or earlier is refused until Tau starts once and migrates it. A Board
29
+ test now reads a store written by the SDK's local backend, so a schema change the board has not
30
+ followed fails the Board suite.
31
+
32
+ ## 1.2.12 — 2026-09-27
33
+
34
+ - Local `/api/chat` sessions now survive a UI reload and a Tau restart. Local mode records each chat
35
+ session and adds `GET /api/local/v1/chat-sessions` to list them and
36
+ `GET /api/local/v1/chat-sessions/{sessionUid}/history` to read one back. The history uses the
37
+ shape of the platform's `GET /api/v1/agent-sessions/{uid}/history/`: user and assistant text,
38
+ reasoning, and tool calls with their arguments, results, and `isError`, projected from the Tau
39
+ transcript by the platform's rules. A turn still running is returned in `inProgressMessage`.
40
+ `GET /api/local/v1/agent` returns the Agent's name and description from the workspace's
41
+ `.agents/agent_card.json`. A local chat turn now runs to its durable end when its client
42
+ disconnects or reloads, and only `POST /api/chat/session/cancel` stops it. A second turn for a
43
+ session that is still running returns 409 `session_busy`. After a Stop, the next turn in a local
44
+ session used to fail with "Runtime cancellation was requested" until the idle runtime was
45
+ evicted; the session's next turn now runs on a fresh runtime. Managed chat behavior is unchanged.
46
+ With `MAINSEQUENCE_TAU_TRUSTED_ORIGINS` set, CORS also exposes `X-Agent-Session-Uid` and
47
+ `x-vercel-ai-ui-message-stream`. See ADR 0018. Fixes #47.
5
48
  - Added `MAINSEQUENCE_TAU_PROVIDER_TIMEOUT_SECONDS`, the HTTP timeout for model-provider calls.
6
49
  It was fixed at 60 seconds for every provider hydrated from the platform, with no setting
7
50
  or environment variable to change it. A self-hosted OpenAI-compatible provider such as an
@@ -9,15 +52,15 @@
9
52
  prompt exhausted all three attempts, and Tau reported only an empty transport failure. The
10
53
  default stays 60 seconds, and an explicit `timeout_seconds` passed to `ProviderFactory.build`
11
54
  still takes precedence.
55
+
56
+ ## 1.2.11 — 2026-09-27
57
+
12
58
  - Added authenticated-process-scoped discovery and bounded hydration for direct local A2A Message
13
59
  conversations. The SDK now persists exact public requester/responder Messages independently from
14
60
  Tau execution entries, returns stable context and Message identities across restart, supports
15
61
  cursor pagination and exact completed-request replay, and exposes
16
62
  `GET /api/local/v1/conversations` plus its bounded Message-history route. Existing internal
17
63
  sessions are not heuristically reconstructed. Fixes #45.
18
-
19
- ## 1.2.11 — 2026-09-27
20
-
21
64
  - Added a version-locked `tau-ai==0.4.2` compatibility patch for OpenAI-compatible transport
22
65
  failures that exhausted retries with an empty terminal message. The SDK now preserves the
23
66
  concrete HTTPX error type, transport phase, retry evidence, total provider duration, one failure
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ms-tau-sdk
3
- Version: 1.2.12.dev20
3
+ Version: 1.2.13.dev22
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
@@ -129,9 +129,9 @@ project selects a provider and model explicitly, while Main Sequence still autho
129
129
  the provider credential and supplies the live MCP catalog:
130
130
 
131
131
  ```bash
132
+ mainsequence login # once; the session is kept by the CLI, not in .env
133
+
132
134
  export MAINSEQUENCE_AUTH_MODE=jwt
133
- export MAINSEQUENCE_ACCESS_TOKEN="<exported-user-access-token>"
134
- export MAINSEQUENCE_REFRESH_TOKEN="<exported-user-refresh-token>"
135
135
  export TAU_LOCAL_MODE=true
136
136
  export TAU_LOCAL_PROVIDER=openai
137
137
  export TAU_LOCAL_MODEL=gpt-5.4
@@ -139,10 +139,21 @@ export TAU_LOCAL_MODEL=gpt-5.4
139
139
  uv run ms-tau
140
140
  ```
141
141
 
142
- The normal Main Sequence login or project launcher may provision those JWT variables, but
143
- `ms-tau-sdk` does not install, import, or invoke the `mainsequence` Python package. It consumes the
144
- environment handoff and public refresh API directly. Provider secrets are never environment
145
- settings and are never persisted locally.
142
+ Log in once with the Main Sequence CLI. Local mode then asks that CLI for a short-lived access
143
+ token with `mainsequence auth token --json`, and again when the token is about to expire. No token
144
+ is exported and none is written to the project `.env`. The SDK looks for the CLI in
145
+ `MAINSEQUENCE_CLI`, then beside the Python interpreter, then on `PATH`. `ms-tau-sdk` does not
146
+ install or import the `mainsequence` Python package and does not read the CLI's credential store.
147
+
148
+ Launchers and CI can hand the process a token pair instead. With both variables set, the SDK
149
+ uses them and the public refresh API and never runs the CLI:
150
+
151
+ ```bash
152
+ export MAINSEQUENCE_ACCESS_TOKEN="<user-access-token>"
153
+ export MAINSEQUENCE_REFRESH_TOKEN="<user-refresh-token>"
154
+ ```
155
+
156
+ Provider secrets are never environment settings and are never persisted locally.
146
157
 
147
158
  Local conversations and public A2A Tasks are stored at
148
159
  `~/.tau/mainsequence/<workspace-hash>/runtime.sqlite3`; an omitted chat `sessionUid` uses the
@@ -172,6 +183,15 @@ events as distinct sections. Correlated provider failures appear first as one hu
172
183
  incident with cause, phase, retries, duration, and bounded traceback frames; raw JSON is an
173
184
  expandable secondary view.
174
185
 
186
+ Local `/api/chat` sessions are reload-safe as well. List them with
187
+ `GET /api/local/v1/chat-sessions` and read one back with
188
+ `GET /api/local/v1/chat-sessions/{sessionUid}/history`, which returns the platform's chat history
189
+ shape: user and assistant text, reasoning, and tool calls with their results, plus a turn still
190
+ running in `inProgressMessage`. Continue a session by sending its `sessionUid` to `POST /api/chat`.
191
+ A local chat turn keeps running when its client disconnects; stop it with
192
+ `POST /api/chat/session/cancel`. `GET /api/local/v1/agent` returns the Agent's name and description
193
+ from the workspace's `.agents/agent_card.json`.
194
+
175
195
  Direct local A2A Message conversations are also reload-safe without becoming Tasks. List them with
176
196
  `GET /api/local/v1/conversations`, then hydrate one with
177
197
  `GET /api/local/v1/conversations/{contextId}/messages`; both reads are bounded and scoped to the
@@ -234,7 +254,8 @@ are not bundled into the SDK. Main Sequence transport and protocol behavior rema
234
254
  ## Included capabilities
235
255
 
236
256
  - FastAPI application construction and lifecycle management
237
- - runtime-credential exchange, local user-JWT refresh, and authenticated Main Sequence access
257
+ - runtime-credential exchange, local sign-in through the Main Sequence CLI session or a user-JWT
258
+ environment pair, and authenticated Main Sequence access
238
259
  - provider validation and credential hydration
239
260
  - durable Tau sessions, leases, restore, persistence, cancellation, eviction, and shutdown
240
261
  - local Tau execution without backend AgentSession pre-creation
@@ -98,9 +98,9 @@ project selects a provider and model explicitly, while Main Sequence still autho
98
98
  the provider credential and supplies the live MCP catalog:
99
99
 
100
100
  ```bash
101
+ mainsequence login # once; the session is kept by the CLI, not in .env
102
+
101
103
  export MAINSEQUENCE_AUTH_MODE=jwt
102
- export MAINSEQUENCE_ACCESS_TOKEN="<exported-user-access-token>"
103
- export MAINSEQUENCE_REFRESH_TOKEN="<exported-user-refresh-token>"
104
104
  export TAU_LOCAL_MODE=true
105
105
  export TAU_LOCAL_PROVIDER=openai
106
106
  export TAU_LOCAL_MODEL=gpt-5.4
@@ -108,10 +108,21 @@ export TAU_LOCAL_MODEL=gpt-5.4
108
108
  uv run ms-tau
109
109
  ```
110
110
 
111
- The normal Main Sequence login or project launcher may provision those JWT variables, but
112
- `ms-tau-sdk` does not install, import, or invoke the `mainsequence` Python package. It consumes the
113
- environment handoff and public refresh API directly. Provider secrets are never environment
114
- settings and are never persisted locally.
111
+ Log in once with the Main Sequence CLI. Local mode then asks that CLI for a short-lived access
112
+ token with `mainsequence auth token --json`, and again when the token is about to expire. No token
113
+ is exported and none is written to the project `.env`. The SDK looks for the CLI in
114
+ `MAINSEQUENCE_CLI`, then beside the Python interpreter, then on `PATH`. `ms-tau-sdk` does not
115
+ install or import the `mainsequence` Python package and does not read the CLI's credential store.
116
+
117
+ Launchers and CI can hand the process a token pair instead. With both variables set, the SDK
118
+ uses them and the public refresh API and never runs the CLI:
119
+
120
+ ```bash
121
+ export MAINSEQUENCE_ACCESS_TOKEN="<user-access-token>"
122
+ export MAINSEQUENCE_REFRESH_TOKEN="<user-refresh-token>"
123
+ ```
124
+
125
+ Provider secrets are never environment settings and are never persisted locally.
115
126
 
116
127
  Local conversations and public A2A Tasks are stored at
117
128
  `~/.tau/mainsequence/<workspace-hash>/runtime.sqlite3`; an omitted chat `sessionUid` uses the
@@ -141,6 +152,15 @@ events as distinct sections. Correlated provider failures appear first as one hu
141
152
  incident with cause, phase, retries, duration, and bounded traceback frames; raw JSON is an
142
153
  expandable secondary view.
143
154
 
155
+ Local `/api/chat` sessions are reload-safe as well. List them with
156
+ `GET /api/local/v1/chat-sessions` and read one back with
157
+ `GET /api/local/v1/chat-sessions/{sessionUid}/history`, which returns the platform's chat history
158
+ shape: user and assistant text, reasoning, and tool calls with their results, plus a turn still
159
+ running in `inProgressMessage`. Continue a session by sending its `sessionUid` to `POST /api/chat`.
160
+ A local chat turn keeps running when its client disconnects; stop it with
161
+ `POST /api/chat/session/cancel`. `GET /api/local/v1/agent` returns the Agent's name and description
162
+ from the workspace's `.agents/agent_card.json`.
163
+
144
164
  Direct local A2A Message conversations are also reload-safe without becoming Tasks. List them with
145
165
  `GET /api/local/v1/conversations`, then hydrate one with
146
166
  `GET /api/local/v1/conversations/{contextId}/messages`; both reads are bounded and scoped to the
@@ -203,7 +223,8 @@ are not bundled into the SDK. Main Sequence transport and protocol behavior rema
203
223
  ## Included capabilities
204
224
 
205
225
  - FastAPI application construction and lifecycle management
206
- - runtime-credential exchange, local user-JWT refresh, and authenticated Main Sequence access
226
+ - runtime-credential exchange, local sign-in through the Main Sequence CLI session or a user-JWT
227
+ environment pair, and authenticated Main Sequence access
207
228
  - provider validation and credential hydration
208
229
  - durable Tau sessions, leases, restore, persistence, cancellation, eviction, and shutdown
209
230
  - local Tau execution without backend AgentSession pre-creation
@@ -9,7 +9,9 @@ from contextlib import closing
9
9
  from pathlib import Path
10
10
  from typing import Any
11
11
 
12
- SCHEMA_VERSION = "5"
12
+ # The version `ms_tau_sdk.backend.local` stamps on the store. The board never imports the SDK,
13
+ # so this follows it by hand.
14
+ SCHEMA_VERSION = "6"
13
15
  TABLES = (
14
16
  "sessions",
15
17
  "entries",
@@ -210,6 +210,7 @@ async function checkConnection() {
210
210
  detailLine(box, "Mode", result.health.mode);
211
211
  detailLine(box, "Version", result.health.version);
212
212
  detailLine(box, "Ready", result.ready ? "Yes" : "No");
213
+ detailLine(box, "Credential source", result.health.mainsequence_auth_source);
213
214
  detailLine(box, "Workspace digest", result.health.workspace_digest);
214
215
  detailLine(box, "State directory", result.stateDir);
215
216
  detailLine(box, "MCP tools", result.health.mcp_tool_count);
@@ -192,7 +192,7 @@
192
192
  <button class="button is-primary" id="settings-apply-board">Apply to this session</button>
193
193
  <p class="help mt-2">This updates the selected profile immediately. It does not write <code>.env</code>.</p>
194
194
  </div>
195
- <div class="box"><h2 class="title is-6">Credential presence</h2><p class="help mb-3">Board process and local <code>.env</code> are shown separately. Credential values are never sent to the browser.</p><div id="settings-credentials" class="detail-list"></div></div>
195
+ <div class="box"><h2 class="title is-6">Credential presence</h2><p class="help mb-3">Board process and local <code>.env</code> are shown separately. Credential values are never sent to the browser. A Tau process that uses the Main Sequence CLI session needs no token here. Connect shows its credential source.</p><div id="settings-credentials" class="detail-list"></div></div>
196
196
  </div>
197
197
  <div class="column">
198
198
  <div class="box">
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "ms-tau-sdk"
7
- version = "1.2.12.dev20"
7
+ version = "1.2.13.dev22"
8
8
  description = "Workspace-bound Tau application primitives for Main Sequence projects"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
@@ -11,14 +11,15 @@ an offline, mock-provider, or mock-MCP mode.
11
11
 
12
12
  ## Start the process
13
13
 
14
- Obtain the user's access and refresh JWTs through the supported Main Sequence login or
15
- project-launcher flow. The SDK consumes that environment handoff directly; it does not install,
16
- import, or invoke the `mainsequence` Python package and does not read its private credential store.
14
+ The user logs in once with a Main Sequence CLI that provides `mainsequence auth token`. Local mode
15
+ then asks that CLI for a short-lived access token, at startup and again when the token is about to
16
+ expire. No token is exported and none is written to the project `.env`. The SDK does not install or
17
+ import the `mainsequence` Python package and does not read the CLI's private credential store.
17
18
 
18
19
  ```bash
20
+ mainsequence login # once; the session is kept by the CLI, not in .env
21
+
19
22
  export MAINSEQUENCE_AUTH_MODE=jwt
20
- export MAINSEQUENCE_ACCESS_TOKEN="<user-access-token>"
21
- export MAINSEQUENCE_REFRESH_TOKEN="<user-refresh-token>"
22
23
  export TAU_LOCAL_MODE=true
23
24
  export TAU_LOCAL_PROVIDER="<provider>"
24
25
  export TAU_LOCAL_MODEL="<model>"
@@ -28,12 +29,20 @@ export TAU_LOCAL_THINKING="<thinking-level>"
28
29
  uv run ms-tau
29
30
  ```
30
31
 
31
- Set `MAINSEQUENCE_ENDPOINT` only for a non-default platform endpoint. Local mode binds to
32
- `127.0.0.1:8787` by default. Treat an explicit public bind as privileged exposure: each accepted
33
- request can use the authenticated user's live Main Sequence permissions.
32
+ The SDK looks for the CLI in `MAINSEQUENCE_CLI`, then beside the Python interpreter that runs
33
+ `ms-tau`, then on `PATH`. Launchers and CI can export `MAINSEQUENCE_ACCESS_TOKEN` and
34
+ `MAINSEQUENCE_REFRESH_TOKEN` instead. With both set, the SDK uses that pair and never runs the
35
+ CLI. Do not add token lines to `.env`. A pair found there still works, but it is deprecated and
36
+ startup logs a warning. `mainsequence refresh-token` run in that directory removes the lines.
37
+
38
+ Set `MAINSEQUENCE_ENDPOINT` only for a non-default platform endpoint. The CLI session must be for
39
+ the same endpoint. Local mode binds to `127.0.0.1:8787` by default. Treat an explicit public bind
40
+ as privileged exposure: each accepted request can use the authenticated user's live Main Sequence
41
+ permissions.
34
42
 
35
43
  Never place JWTs or provider credentials in source control, `.tau`, copied skills, command output,
36
- or debugging artifacts. The user JWT is sent only to Main Sequence. Main Sequence validates the
44
+ or debugging artifacts. Do not run `mainsequence auth token` to read its output: it prints the
45
+ access token. The user JWT is sent only to Main Sequence. Main Sequence validates the
37
46
  explicit `TAU_LOCAL_PROVIDER` and `TAU_LOCAL_MODEL`, returns provider-control evidence, and hydrates
38
47
  the provider credential used for inference. Provider secrets are neither environment settings nor
39
48
  local state.
@@ -45,7 +54,7 @@ local state.
45
54
  | Agent or AgentSession registration | Never created or updated |
46
55
  | Chat history, snapshots, leases, activity, and cancellation | Workspace SQLite |
47
56
  | Public A2A Message and Task state | Workspace SQLite |
48
- | Main Sequence authentication and token refresh | Remote, using the exported JWT pair |
57
+ | Main Sequence authentication and token refresh | Remote. The token comes from the Main Sequence CLI session, or from an exported JWT pair |
49
58
  | Provider authorization, hydration, and inference | Remote and real |
50
59
  | Main Sequence MCP catalog and tool calls | Remote and real; platform mutations remain possible |
51
60
  | Project instructions, skills, hooks, and extensions | Normal repository `.tau` composition |
@@ -81,6 +90,16 @@ a platform AgentSession. Local mode supports:
81
90
  rescheduling safe unclaimed work and terminalizing stale uncertain work rather than replaying
82
91
  possible side effects.
83
92
 
93
+ For a chat UI over `POST /api/chat`, do not read `runtime.sqlite3` or keep a browser-only
94
+ transcript either. List sessions with `GET /api/local/v1/chat-sessions?limit=<n>&cursor=<opaque>`
95
+ and hydrate one with `GET /api/local/v1/chat-sessions/{sessionUid}/history`. The history has the
96
+ platform's `GET /api/v1/agent-sessions/{uid}/history/` shape, including reasoning, tool calls with
97
+ results, and a running turn in `inProgressMessage`, so one reader serves local and deployed
98
+ Agents. Reuse the listed `sessionUid` in `POST /api/chat`. A local chat turn keeps running when
99
+ the client disconnects or reloads; stop it only with `POST /api/chat/session/cancel`, and queue a
100
+ new message while a session is `working` (`POST /api/chat` returns 409 `session_busy`). Title the
101
+ UI from `GET /api/local/v1/agent`, which reads the workspace's `.agents/agent_card.json`.
102
+
84
103
  For the default direct Message flow, do not read `runtime.sqlite3` or keep a browser-only source of
85
104
  truth. Discover conversations with `GET /api/local/v1/conversations?limit=<n>&cursor=<opaque>` and
86
105
  hydrate the latest bounded Message tail with
@@ -179,7 +198,8 @@ platform resources under the authenticated user's permissions.
179
198
 
180
199
  Check the process in this order:
181
200
 
182
- 1. `GET /health` reports `mode: local` and safe composition diagnostics.
201
+ 1. `GET /health` reports `mode: local`, safe composition diagnostics, and
202
+ `mainsequence_auth_source`: `cli`, `environment`, or `env_file`.
183
203
  2. `GET /ready` confirms user authentication, provider control, local storage, and MCP readiness.
184
204
  3. `GET /version` reports the expected installed SDK release.
185
205
  4. A chat request without `sessionUid` returns an effective identifier in
@@ -193,8 +213,10 @@ Check the process in this order:
193
213
  ## Classify failures
194
214
 
195
215
  - Startup setting failure: inspect the exact missing or conflicting environment variable.
196
- - Authentication failure: refresh or re-export the JWT pair; do not substitute a runtime
197
- credential in local mode.
216
+ - Authentication failure: read the error, which names its remedy. The user runs
217
+ `mainsequence login`, upgrades a Main Sequence CLI that is too old for `auth token`, sets
218
+ `MAINSEQUENCE_CLI`, or provides the JWT pair. With an exported pair, export a fresh one. Do not
219
+ substitute a runtime credential in local mode.
198
220
  - Provider/model rejection: verify the exact configured names against the authenticated live
199
221
  catalog; do not silently select a different model.
200
222
  - OpenAI-compatible transport failure: open the Task's failure incident in Tau Board. Use its
@@ -95,11 +95,24 @@ async def chat(
95
95
  message_count=len(body.messages) if body.messages else None,
96
96
  ),
97
97
  )
98
+ # A local turn is recorded as a chat session and outlives this request, so a
99
+ # reload or a closed tab leaves it running; only session/cancel stops it. A
100
+ # managed turn stays bound to the request that streams it.
101
+ local_turn = (
102
+ await manager.start_local_chat_turn(session_uid, prompt, provenance=provenance)
103
+ if manager.settings.local_mode
104
+ else None
105
+ )
106
+ events = (
107
+ local_turn.events()
108
+ if local_turn is not None
109
+ else manager.prompt(session_uid, prompt, provenance=provenance)
110
+ )
98
111
 
99
112
  async def stream() -> AsyncIterator[bytes]:
100
113
  encoder = AssistantUiEncoder()
101
114
  try:
102
- async for event in manager.prompt(session_uid, prompt, provenance=provenance):
115
+ async for event in events:
103
116
  for payload in encoder.encode(event):
104
117
  yield encoder.sse(payload)
105
118
  try:
@@ -107,9 +120,10 @@ async def chat(
107
120
  yield encoder.sse(payload)
108
121
  yield encoder.done()
109
122
  finally:
110
- manager.mark_response_delivered(session_uid)
123
+ if local_turn is None:
124
+ manager.mark_response_delivered(session_uid)
111
125
  except asyncio.CancelledError:
112
- if not encoder.finished:
126
+ if local_turn is None and not encoder.finished:
113
127
  await manager.cancel(session_uid)
114
128
  raise
115
129
  except Exception as error:
@@ -144,6 +158,9 @@ async def chat(
144
158
  }
145
159
  )
146
160
  yield encoder.done()
161
+ finally:
162
+ if local_turn is not None:
163
+ local_turn.detach()
147
164
 
148
165
  return StreamingResponse(
149
166
  stream(),
@@ -0,0 +1,256 @@
1
+ """Reload-safe local `/api/chat` sessions and the local Agent's identity (ADR 0018)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import json
7
+ from dataclasses import dataclass
8
+ from datetime import datetime
9
+ from pathlib import Path
10
+ from typing import Annotated, Any
11
+
12
+ import structlog
13
+ from fastapi import APIRouter, Depends, HTTPException, Query
14
+
15
+ from ms_tau_sdk.backend.client import MainSequenceClient
16
+ from ms_tau_sdk.backend.models import LocalChatSessionSummary
17
+ from ms_tau_sdk.protocols.chat_history import (
18
+ HISTORY_VERSION,
19
+ HistoryMessage,
20
+ current_branch,
21
+ iso_timestamp,
22
+ latest_message_preview,
23
+ project_history,
24
+ )
25
+ from ms_tau_sdk.runtime.manager import SessionRuntimeManager
26
+ from ms_tau_sdk.settings import TauSDKSettings
27
+
28
+ from .dependencies import backend, runtime_manager, settings
29
+
30
+ router = APIRouter(prefix="/api/local/v1")
31
+ BackendDep = Annotated[MainSequenceClient, Depends(backend)]
32
+ RuntimeManagerDep = Annotated[SessionRuntimeManager, Depends(runtime_manager)]
33
+ SettingsDep = Annotated[TauSDKSettings, Depends(settings)]
34
+ logger = structlog.get_logger(__name__)
35
+
36
+ AGENT_CARD_PATH = Path(".agents") / "agent_card.json"
37
+ LOCAL_AGENT_NAME = "Local Main Sequence TAU Agent"
38
+
39
+
40
+ def _require_local(config: TauSDKSettings) -> None:
41
+ if not config.local_mode:
42
+ raise HTTPException(
43
+ status_code=409,
44
+ detail="Local chat sessions require TAU_LOCAL_MODE=true",
45
+ )
46
+
47
+
48
+ @dataclass(frozen=True, slots=True)
49
+ class AgentIdentity:
50
+ name: str | None = None
51
+ description: str | None = None
52
+
53
+
54
+ def _read_agent_identity(workspace: Path) -> AgentIdentity:
55
+ """Read the name and description of the workspace's `.agents/agent_card.json`.
56
+
57
+ The platform takes a deployed Agent's name and description from the same file.
58
+ A missing or unreadable card leaves both unset rather than inventing them.
59
+ """
60
+
61
+ path = workspace / AGENT_CARD_PATH
62
+ try:
63
+ card = json.loads(path.read_text(encoding="utf-8"))
64
+ except FileNotFoundError:
65
+ return AgentIdentity()
66
+ except (OSError, UnicodeDecodeError, json.JSONDecodeError) as error:
67
+ logger.warning(
68
+ "local_agent.card_unreadable",
69
+ message="The workspace Agent Card could not be read",
70
+ path=str(AGENT_CARD_PATH),
71
+ error_type=type(error).__name__,
72
+ )
73
+ return AgentIdentity()
74
+ if not isinstance(card, dict):
75
+ return AgentIdentity()
76
+
77
+ def text(key: str) -> str | None:
78
+ value = card.get(key)
79
+ return value.strip() if isinstance(value, str) and value.strip() else None
80
+
81
+ return AgentIdentity(name=text("name"), description=text("description"))
82
+
83
+
84
+ async def workspace_agent_identity(workspace: Path) -> AgentIdentity:
85
+ return await asyncio.to_thread(_read_agent_identity, workspace)
86
+
87
+
88
+ @dataclass(frozen=True, slots=True)
89
+ class _SessionProjection:
90
+ summary: LocalChatSessionSummary
91
+ messages: list[HistoryMessage]
92
+ in_progress: HistoryMessage | None
93
+ status: str
94
+ error: str | None
95
+ updated_at: datetime
96
+
97
+
98
+ async def _project_session(
99
+ client: MainSequenceClient,
100
+ manager: SessionRuntimeManager,
101
+ config: TauSDKSettings,
102
+ session_uid: str,
103
+ ) -> _SessionProjection:
104
+ """Project a session's durable branch plus the turn this process is running.
105
+
106
+ The running turn is read before the durable entries. If that turn committed in
107
+ between, its durable entries already hold everything the live view had.
108
+ """
109
+
110
+ live_turn = manager.live_turn(session_uid)
111
+ live_entries = live_turn.entries() if live_turn is not None else None
112
+ transcript = await client.get_local_chat_transcript(
113
+ session_uid,
114
+ turn_uid=live_turn.turn_uid if live_turn is not None else None,
115
+ )
116
+ if transcript.turn_committed:
117
+ live_entries = None
118
+ durable = [
119
+ item.entry
120
+ for item in transcript.entries
121
+ if live_entries is None or live_turn is None or item.turn_uid != live_turn.turn_uid
122
+ ]
123
+ branch = current_branch(durable)
124
+ projected = project_history(
125
+ [*branch, *(live_entries or [])],
126
+ target_agent_uid=config.local_agent_uid,
127
+ )
128
+ messages = projected.messages
129
+ in_progress: HistoryMessage | None = None
130
+ if (
131
+ live_entries is not None
132
+ and messages
133
+ and messages[-1]["role"] == "assistant"
134
+ and projected.source_indexes[-1] >= len(branch)
135
+ ):
136
+ # The running turn's newest assistant message, streaming or awaiting a tool.
137
+ in_progress = {**messages.pop(), "completedAt": None}
138
+ running = (
139
+ live_entries is not None
140
+ or transcript.session.working
141
+ or manager.local_session_running(session_uid)
142
+ )
143
+ status = "running" if running else ("error" if projected.last_turn_error else "completed")
144
+ updated_at = transcript.session.updated_at
145
+ if projected.last_timestamp is not None:
146
+ updated_at = max(updated_at, projected.last_timestamp)
147
+ return _SessionProjection(
148
+ summary=transcript.session,
149
+ messages=messages,
150
+ in_progress=in_progress,
151
+ status=status,
152
+ error=projected.last_turn_error if status == "error" else None,
153
+ updated_at=updated_at,
154
+ )
155
+
156
+
157
+ def _session_summary(
158
+ summary: LocalChatSessionSummary,
159
+ *,
160
+ message_count: int,
161
+ latest_message_preview: str | None,
162
+ updated_at: datetime,
163
+ working: bool,
164
+ ) -> dict[str, Any]:
165
+ return {
166
+ "sessionUid": summary.session_uid,
167
+ "title": summary.title,
168
+ "messageCount": message_count,
169
+ "latestMessagePreview": latest_message_preview,
170
+ "createdAt": iso_timestamp(summary.created_at),
171
+ "updatedAt": iso_timestamp(updated_at),
172
+ "working": working,
173
+ }
174
+
175
+
176
+ @router.get("/chat-sessions")
177
+ async def list_chat_sessions(
178
+ client: BackendDep,
179
+ manager: RuntimeManagerDep,
180
+ config: SettingsDep,
181
+ limit: int = Query(default=50, ge=1, le=100),
182
+ cursor: str | None = Query(default=None, min_length=1, max_length=1024),
183
+ ) -> dict[str, Any]:
184
+ _require_local(config)
185
+ try:
186
+ page = await client.list_local_chat_sessions(limit=limit, cursor=cursor)
187
+ except ValueError as error:
188
+ raise HTTPException(status_code=400, detail=str(error)) from error
189
+ sessions: list[dict[str, Any]] = []
190
+ for summary in page.sessions:
191
+ if manager.live_turn(summary.session_uid) is None:
192
+ sessions.append(
193
+ _session_summary(
194
+ summary,
195
+ message_count=summary.message_count,
196
+ latest_message_preview=summary.latest_message_preview,
197
+ updated_at=summary.updated_at,
198
+ working=(summary.working or manager.local_session_running(summary.session_uid)),
199
+ )
200
+ )
201
+ continue
202
+ projection = await _project_session(client, manager, config, summary.session_uid)
203
+ visible = [
204
+ *projection.messages,
205
+ *([projection.in_progress] if projection.in_progress is not None else []),
206
+ ]
207
+ sessions.append(
208
+ _session_summary(
209
+ summary,
210
+ message_count=len(visible),
211
+ latest_message_preview=latest_message_preview(visible),
212
+ updated_at=max(summary.updated_at, projection.updated_at),
213
+ working=projection.status == "running",
214
+ )
215
+ )
216
+ return {"sessions": sessions, "nextCursor": page.next_cursor}
217
+
218
+
219
+ @router.get("/chat-sessions/{session_uid}/history")
220
+ async def chat_session_history(
221
+ session_uid: str,
222
+ client: BackendDep,
223
+ manager: RuntimeManagerDep,
224
+ config: SettingsDep,
225
+ ) -> dict[str, Any]:
226
+ _require_local(config)
227
+ canonical = config.local_session_uid(session_uid)
228
+ projection = await _project_session(client, manager, config, canonical)
229
+ identity = await workspace_agent_identity(config.workspace)
230
+ return {
231
+ "version": HISTORY_VERSION,
232
+ "session": {
233
+ "sessionId": canonical,
234
+ "threadId": canonical,
235
+ "agentName": identity.name or LOCAL_AGENT_NAME,
236
+ "agentUid": config.local_agent_uid,
237
+ "agentSessionUid": canonical,
238
+ "status": projection.status,
239
+ "startedAt": iso_timestamp(projection.summary.created_at),
240
+ "updatedAt": iso_timestamp(projection.updated_at),
241
+ "error": projection.error,
242
+ },
243
+ "messages": projection.messages,
244
+ "inProgressMessage": projection.in_progress,
245
+ }
246
+
247
+
248
+ @router.get("/agent")
249
+ async def local_agent(config: SettingsDep) -> dict[str, str | None]:
250
+ _require_local(config)
251
+ identity = await workspace_agent_identity(config.workspace)
252
+ return {
253
+ "name": identity.name,
254
+ "displayName": identity.name,
255
+ "description": identity.description,
256
+ }