ac-teams 0.0.7 → 0.0.8
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.
- package/README.md +1 -1
- package/docs/access.md +4 -0
- package/docs/design/session-roles/README.md +343 -0
- package/docs/design/session-roles/coordinator.yaml +44 -0
- package/docs/design/session-roles/design.yaml +70 -0
- package/docs/design/session-roles/herdr.yaml +17 -0
- package/docs/design/session-roles/journey.md +139 -0
- package/docs/design/session-roles/main-session.yaml +34 -0
- package/docs/design/session-roles/member.yaml +28 -0
- package/docs/design/session-roles/read-guard.yaml +17 -0
- package/docs/design/session-roles/team-store.yaml +13 -0
- package/docs/design/session-roles/transcripts.yaml +23 -0
- package/docs/design/session-roles/user.yaml +17 -0
- package/docs/operations.md +13 -2
- package/docs/usage.md +13 -2
- package/package.json +1 -1
- package/src/commands.ts +1 -1
- package/src/coordinator.ts +241 -30
- package/src/index.ts +238 -57
- package/src/mains.ts +41 -0
- package/src/panel.ts +9 -1
- package/src/schemas.ts +2 -2
- package/src/store.ts +32 -9
- package/src/terminal-agent.ts +1 -1
- package/src/terminal-ui.ts +6 -3
- package/src/types.ts +19 -4
- package/src/ui-types.ts +4 -0
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ Your Pi session is the root; its child agents are members. Ask Pi to delegate a
|
|
|
28
28
|
|
|
29
29
|
The task summary sits above the input. The agent selector sits below the input, above your existing footer, and shows descriptive names, models, and context usage. ac-teams publishes a compact team status without replacing the footer or editor supplied by other extensions. `/teams config` toggles these displays independently.
|
|
30
30
|
|
|
31
|
-
From an empty editor, press Down to select the team row, then Enter to open a member. A red **Needs input** label marks a member with an open question. Ctrl+Shift+Left returns to Main without stopping the member or dismissing its question. `/teams stop` stops every
|
|
31
|
+
From an empty editor, press Down to select the team row, then Enter to open a member. A red **Needs input** label marks a member with an open question. Ctrl+Shift+Left returns to Main without stopping the member or dismissing its question. `/teams stop` stops every member; main sessions keep running. `/teams join` and `/teams leave` add this session to another team or take it out.
|
|
32
32
|
|
|
33
33
|
Defaults allow up to **10 live members** and **two levels of delegation**. Use the fewest useful agents.
|
|
34
34
|
|
package/docs/access.md
CHANGED
|
@@ -84,6 +84,10 @@ docker build -t ac-teams-member:1.5.0-<hash> --build-arg PI_VERSION=1.5.0 -f <ac
|
|
|
84
84
|
|
|
85
85
|
The image is `node:22-bookworm-slim`, pinned by digest, with bash, curl, Git, ripgrep, CA certificates, and the same Pi version as Main. Rebuild after upgrading Pi.
|
|
86
86
|
|
|
87
|
+
## Promotion drops enforcement
|
|
88
|
+
|
|
89
|
+
Promoting an enforced member to a main session relaunches it outside the container with your user's access. The panel asks for confirmation first. Members always run with `ACSTACK_AGENT_ROLE=member`; main sessions run without it and without inherited `AC_TEAMS_*` variables.
|
|
90
|
+
|
|
87
91
|
## Storage
|
|
88
92
|
|
|
89
93
|
- Coordinator state and settings: `~/.pi/ac-teams/teams/<team>/`, `~/.pi/ac-teams/config.json`, and `~/.pi/ac-teams/display.json`
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
# Session roles - promotion, joining, and several main sessions
|
|
2
|
+
|
|
3
|
+
A team can have several main sessions. The user can promote a member into a main session or join an existing session to a team. Identity, transcript discovery, membership, coordinator ownership, and permission authority stay separate, and ACSTACK\_AGENT\_ROLE tells hooks which processes are main.
|
|
4
|
+
|
|
5
|
+
<!-- Generated from the entry YAML and its element files; regenerate with design-doc --markdown -->
|
|
6
|
+
|
|
7
|
+
```mermaid
|
|
8
|
+
flowchart TD
|
|
9
|
+
element_user["User<br/>actor"]
|
|
10
|
+
element_main-session["Main session<br/>component · cli"]
|
|
11
|
+
element_member["Member<br/>component"]
|
|
12
|
+
element_coordinator["Coordinator<br/>component"]
|
|
13
|
+
element_team-store["Team store<br/>store"]
|
|
14
|
+
element_transcripts["Pi transcripts<br/>store"]
|
|
15
|
+
element_herdr["Herdr<br/>external-system"]
|
|
16
|
+
element_read-guard["ACStack read guard<br/>external-system"]
|
|
17
|
+
element_user -->|"Promotes, joins, and leaves through /teams in"| element_main-session
|
|
18
|
+
element_user -->|"Switches between main sessions in"| element_herdr
|
|
19
|
+
element_main-session -->|"Hosts as owner, or connects to as a client"| element_coordinator
|
|
20
|
+
element_main-session -->|"Writes its conversation and team entry to"| element_transcripts
|
|
21
|
+
element_read-guard -->|"Finds no member role in"| element_main-session
|
|
22
|
+
element_coordinator -->|"Relaunches promoted members as"| element_main-session
|
|
23
|
+
element_coordinator -->|"Launches, stops, and resumes"| element_member
|
|
24
|
+
element_member -->|"Calls team tools through"| element_coordinator
|
|
25
|
+
element_member -->|"Writes its conversation to"| element_transcripts
|
|
26
|
+
element_read-guard -->|"Reads ACSTACK_AGENT_ROLE=member in"| element_member
|
|
27
|
+
element_coordinator -->|"Holds owner.lock and records roles in"| element_team-store
|
|
28
|
+
element_coordinator -->|"Moves promoted transcripts within"| element_transcripts
|
|
29
|
+
element_coordinator -->|"Opens main-session tabs through"| element_herdr
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Session roles: promotion, joining, and several main sessions
|
|
33
|
+
|
|
34
|
+
Status: draft for review. Implements nothing yet. Resolves the design questions in [issue #19](https://github.com/ac8318740/ac-teams/issues/19).
|
|
35
|
+
|
|
36
|
+
A team can have several **main sessions**. A main session is a Pi session the user created, joined to a team, or promoted. Every other team process is a **member**. The user can promote a member to a main session from the TUI, or join an existing session to a team with `/teams join`. ac-teams marks every member process with `ACSTACK_AGENT_ROLE=member`, so ACStack's large-read guard applies only to main sessions.
|
|
37
|
+
|
|
38
|
+
### Review questions
|
|
39
|
+
|
|
40
|
+
1. Does promotion by relaunch feel seamless enough, given that a promoted session moves to its own Herdr tab?
|
|
41
|
+
2. Does it look right that every main session can control every member and other main sessions? That matches what an unsandboxed session can already do through its shell.
|
|
42
|
+
3. Is the takeover after the owner exits (automatic, with a notice) the right default?
|
|
43
|
+
4. Are the changes and tests at the end complete enough to start implementation?
|
|
44
|
+
|
|
45
|
+
### Trade-offs to judge
|
|
46
|
+
|
|
47
|
+
- **Relaunch, not live handover.** Promotion stops the member process and reopens its transcript in a new process. The member loses its in-flight turn, but its role, sandbox, token, and environment change cleanly.
|
|
48
|
+
- **No coordinator daemon.** The coordinator stays inside whichever main session holds `owner.lock`. When the owner exits, members stop and the next owner resumes them automatically. The cost is a pause and at most one redone turn per busy member.
|
|
49
|
+
- **Wide main-session authority.** Any main session can stop work started by another. ac-teams does not try to guard against this, because an unsandboxed session can already stop processes through the shell or Herdr.
|
|
50
|
+
|
|
51
|
+
## Definitions
|
|
52
|
+
|
|
53
|
+
These five properties are independent. A change to one never changes another.
|
|
54
|
+
|
|
55
|
+
| Property | Defined by | Changes when |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| Identity | Pi session id in the transcript header | Never |
|
|
58
|
+
| Discovery | Where the transcript file lives | Promotion moves it to the top-level session folder |
|
|
59
|
+
| Membership | A member record in the team store | Join adds one; leave clears its role |
|
|
60
|
+
| Coordinator ownership | Holder of `owner.lock` | The owner exits and another main session takes it |
|
|
61
|
+
| Authority | The record's `role`: `main` or `member` | Only promotion, join, or leave, all user actions |
|
|
62
|
+
|
|
63
|
+
### What the coordinator does
|
|
64
|
+
|
|
65
|
+
The coordinator is ac-teams' in-process server. Today it runs inside Main. In this design, it runs inside whichever main session holds `owner.lock`. In practice it:
|
|
66
|
+
|
|
67
|
+
- **Runs member processes.** Starts each member's Pi, either headless or in tmux, optionally inside a Docker sandbox. It also stops and resumes members, and stops them all when its host session exits
|
|
68
|
+
- **Answers team tools.** Every `team_agent`, `team_task`, and `team_message` call from any session arrives over `coordinator.sock` and is applied here, so concurrent edits are serialized
|
|
69
|
+
- **Keeps team state.** Writes members, tasks, and messages to the team store
|
|
70
|
+
- **Delivers notifications.** Tells each session when new team messages arrive
|
|
71
|
+
- **Enforces rules.** Checks authority, depth and member limits, and sandbox grants, and brokers provider credentials for sandboxed members
|
|
72
|
+
|
|
73
|
+
This design keeps all of that. It adds role checks, connections from main sessions it did not launch, promotion, joining, leaving, and ownership takeover.
|
|
74
|
+
|
|
75
|
+
The team id stays the session id of the session that created the team, even after that session leaves or hands over ownership.
|
|
76
|
+
|
|
77
|
+
### Main-agent signal
|
|
78
|
+
|
|
79
|
+
- The coordinator sets `ACSTACK_AGENT_ROLE=member` on every member process, next to `AC_TEAMS_MEMBER`. That includes the terminal-launch script, which today only passes `AC_TEAMS_*` and `HERDR_ENV`
|
|
80
|
+
- A hook treats the session as main when the variable is absent or `main`, and as not main for any other value
|
|
81
|
+
- Anything a member starts inherits the variable and counts as not main
|
|
82
|
+
- Promotion and every main-session launch must remove `ACSTACK_AGENT_ROLE`, every `AC_TEAMS_*` variable, and `HERDR_ENV=0`. Tests must cover this
|
|
83
|
+
- ACStack reads the variable on every tool call. It detects Claude Code and Codex subagents from `agent_id` in their hook payloads instead. That detection belongs to ACStack, not ac-teams
|
|
84
|
+
- Role grants no permission by itself
|
|
85
|
+
|
|
86
|
+
## User flows
|
|
87
|
+
|
|
88
|
+
### Promote a member
|
|
89
|
+
|
|
90
|
+
1. The user opens a teammate from the agent selector or `/teams` and chooses **Promote to main**
|
|
91
|
+
2. If the member is sandboxed, a confirmation states that the session will run with the user's full access. Declining cancels
|
|
92
|
+
3. The coordinator stops the member, waiting for its process to exit as `stop` does today
|
|
93
|
+
4. The coordinator moves the transcript from `ac-teams/<team>/<member>/` to the project's top-level session folder. It removes the member's config at `~/.pi/ac-teams/agents/<id>` and sets the member's role to `main` without a parent. The member's children stay its descendants
|
|
94
|
+
5. The coordinator writes the session's team entry: a Pi custom entry holding the team id and member id, with no secrets
|
|
95
|
+
6. The coordinator relaunches `pi --session <moved file>` with a clean environment:
|
|
96
|
+
- With Herdr, in a new tab in the workspace for the session's worktree, creating the workspace if needed. The tab gets focus and the session's name
|
|
97
|
+
- Without Herdr, in a tmux session on a per-main-session socket that the owner's `kill-server` cannot reach. The user's screen stays attached, and ac-teams prints the reattach command
|
|
98
|
+
7. The new process reads its team entry, connects to the coordinator with a main-session token from the team store, and appears in `/resume`
|
|
99
|
+
|
|
100
|
+
### Join an existing session to a team
|
|
101
|
+
|
|
102
|
+
1. The user runs `/teams join` in an ordinary Pi session
|
|
103
|
+
2. ac-teams lists this project's teams. The user picks one
|
|
104
|
+
3. ac-teams refuses if the session already belongs to a team with members, or if its session id is already in the chosen team
|
|
105
|
+
4. The coordinator adds a member record with role `main` and writes the team entry
|
|
106
|
+
5. The session receives a short summary of open tasks, owners, and recent messages, with its message cursor at the latest message. It claims or creates work with the normal team tools
|
|
107
|
+
|
|
108
|
+
### Several main sessions
|
|
109
|
+
|
|
110
|
+
- Each main session holds one connection to the coordinator. The owner hosts it in-process; the others connect over `coordinator.sock`
|
|
111
|
+
- Every main session sits at depth 0. Any main session can launch, stop, resume, and clean up any member, and stop or resume another main session
|
|
112
|
+
- Stopping a main session asks it to shut down through its connection. Resuming one relaunches its transcript, as in step 6 of promotion
|
|
113
|
+
- Members keep today's rule: they control only their descendants and never a main session
|
|
114
|
+
- Tasks and messages stay shared. Ownership and revision checks still serialize concurrent edits, because one coordinator applies them all
|
|
115
|
+
|
|
116
|
+
### Leave a team
|
|
117
|
+
|
|
118
|
+
`/teams leave` returns the session's in-progress tasks to todo and clears their ownership. The member record keeps its history but loses its role, so its descendants stay under the other main sessions. The team entry records the departure, so a later `/resume` does not rejoin. Leaving is refused while the session is the only main session and members are still live.
|
|
119
|
+
|
|
120
|
+
## Recovery
|
|
121
|
+
|
|
122
|
+
- **The owner exits while another main session runs.** Before stopping members, the owner records each one's state (busy, idle, or needs input) in the team store. Today, `Coordinator.open` discards that state. A connected main session then takes `owner.lock`, opens the coordinator, and runs the resume pass below. Every main session gets a notice naming the new owner
|
|
123
|
+
- **No main session is running when the owner exits.** Members stay stopped with their recorded state. The next main session that opens the team runs the same resume pass
|
|
124
|
+
- **A promoted session is resumed with no coordinator running.** The extension reads its team entry at startup and offers to take ownership. Accepting opens the coordinator and runs the resume pass. Declining runs the session without team tools
|
|
125
|
+
|
|
126
|
+
### Resume pass
|
|
127
|
+
|
|
128
|
+
| Recorded state | Action |
|
|
129
|
+
|---|---|
|
|
130
|
+
| Busy | Resume and send: "Your process was restarted during a coordinator handover. Check your last steps and continue your current task." |
|
|
131
|
+
| Idle | Resume and leave idle |
|
|
132
|
+
| Needs input | Resume and show **Needs input** again |
|
|
133
|
+
|
|
134
|
+
Pi saves each completed message, so a busy member loses only the reply it was generating and redoes that turn. A command killed mid-run may have half finished. The message tells the member to check before it continues, which reduces that risk but does not remove it, the same as stopping a member today.
|
|
135
|
+
- **A main session crashes.** Its record stays. The coordinator marks it interrupted, and any main session can resume it
|
|
136
|
+
- **Promotion fails partway.** Each step is recorded in the team store before the next starts. On failure, the coordinator rolls back to a stopped member with its original transcript path, and the member can be retried or resumed
|
|
137
|
+
|
|
138
|
+
## Security boundaries
|
|
139
|
+
|
|
140
|
+
- Only the user can promote, join, or leave. These actions have no agent-tool form
|
|
141
|
+
- Promoting a sandboxed member is an explicit escalation, approved in the confirmation step. Without approval, nothing changes
|
|
142
|
+
- Main-session tokens live in the private team store, which sandboxed members cannot read. A member therefore cannot impersonate a main session over the socket
|
|
143
|
+
- Descendant non-escalation stays as it is: a member cannot grant its descendants more access than it has
|
|
144
|
+
- Provider credentials for sandboxed members come from the current owner's Pi login. After a takeover, the new owner brokers them under the same OS user
|
|
145
|
+
|
|
146
|
+
## Changes needed before implementation
|
|
147
|
+
|
|
148
|
+
- **Team store schema 4.** Add `role` to members and migrate `root` to a main record. Replace `actor === "root"` checks with role checks. Store each main session's token
|
|
149
|
+
- **Coordinator.**
|
|
150
|
+
- Accept connections from main sessions that were not launched by the coordinator
|
|
151
|
+
- Add promote, join, leave, and the stop and resume of main sessions
|
|
152
|
+
- Add ownership takeover, and recompute descendant depths after a promotion
|
|
153
|
+
- **Launch environment.** Set `ACSTACK_AGENT_ROLE=member` for every member, including the terminal script's variable filter. Strip it on main-session launches
|
|
154
|
+
- **Extension.** Read the team entry at `session_start`. Add `/teams join`, `/teams leave`, and the **Promote to main** action. Open Herdr tabs, or use the tmux fallback
|
|
155
|
+
- **Docs.** Update `docs/usage.md`, `docs/operations.md`, and `docs/access.md`
|
|
156
|
+
|
|
157
|
+
## Tests needed
|
|
158
|
+
|
|
159
|
+
- A launched member has `ACSTACK_AGENT_ROLE=member`, in both RPC and terminal modes
|
|
160
|
+
- A promoted session's environment has no `ACSTACK_AGENT_ROLE` and no `AC_TEAMS_*` variables
|
|
161
|
+
- Promotion keeps the session id, moves the file, updates the stored path, and writes the team entry. Pi's session listing then includes the session
|
|
162
|
+
- A sandboxed promotion is refused without confirmation and runs unsandboxed after it
|
|
163
|
+
- Joining refuses a duplicate session id, and refuses a session that leads another team with members
|
|
164
|
+
- A main session can stop and resume members and other main sessions. A member cannot act on a main session
|
|
165
|
+
- When the owner exits, another main session takes the lock. Busy members resume with the continue message, idle members stay idle, and members that needed input show **Needs input** again
|
|
166
|
+
- When no main session is running, the next one to open the team runs the same resume pass
|
|
167
|
+
- Resuming a promoted transcript with no coordinator offers takeover, and declining leaves team tools off
|
|
168
|
+
- Leaving returns in-progress tasks to todo and keeps descendants manageable
|
|
169
|
+
- A promotion interrupted after each step rolls back to a resumable member
|
|
170
|
+
- Schema 3 teams migrate to schema 4 with root as a main record
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
## User
|
|
174
|
+
|
|
175
|
+
*Starts main sessions and is the only party who can promote a member, join a session to a team, or approve a sandboxed member's full access.*
|
|
176
|
+
|
|
177
|
+
- Identity: `user`
|
|
178
|
+
- Kind: actor
|
|
179
|
+
- Channels: none
|
|
180
|
+
- Source: [user.yaml](user.yaml)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
## Main session
|
|
185
|
+
|
|
186
|
+
*A Pi session the user started, joined to a team, or promoted. Every main session controls every member and other main sessions, shares the team's tasks and messages, and belongs to one team at a time.*
|
|
187
|
+
|
|
188
|
+
- Identity: `main-session`
|
|
189
|
+
- Kind: component
|
|
190
|
+
- Channels: cli
|
|
191
|
+
- Source: [main-session.yaml](main-session.yaml)
|
|
192
|
+
|
|
193
|
+
## Main-agent signal
|
|
194
|
+
|
|
195
|
+
- Requirement: ACStack's large-read guard applies only to main agents. A main agent is a session the user created or promoted, never one an agent created. The signal must reach in-process Pi hooks and shell hooks, and must change when a member is promoted.
|
|
196
|
+
- Design response: The coordinator sets ACSTACK\_AGENT\_ROLE=member on every member process, next to AC\_TEAMS\_MEMBER. Absent or "main" means main; any other value means not main. Children of a member inherit it. Promotion always relaunches the transcript in a new process without ACSTACK\_AGENT\_ROLE or any AC\_TEAMS\_\* variable, so a live process never needs to change its role. Main status grants no permissions.
|
|
197
|
+
- Applies to: coordinator, member, main-session, read-guard
|
|
198
|
+
|
|
199
|
+
## Separate identity, discovery, membership, and authority
|
|
200
|
+
|
|
201
|
+
- Requirement: Issue \#19 forbids tying session identity or permission authority to picker visibility, storage location, or a single main session.
|
|
202
|
+
- Design response: Identity is the Pi session id. Discovery is where the transcript file lives. Membership and role are records in the team store. Ownership is whoever holds owner.lock. Authority comes from the member's role in the team store, which only explicit user actions change. Moving a file or taking the lock changes nothing else.
|
|
203
|
+
- Applies to: main-session, member, coordinator, team-store, transcripts
|
|
204
|
+
|
|
205
|
+
## Duplicate prevention
|
|
206
|
+
|
|
207
|
+
- Requirement: A session must not appear twice in a team, run as two processes, or belong to two teams.
|
|
208
|
+
- Design response: The team store keys members by member id and refuses a second record with the same Pi session id. The coordinator accepts one live connection per member and keeps the existing pid and identity check before any launch or relaunch. A session in a team with members cannot join another team.
|
|
209
|
+
- Applies to: coordinator, team-store, main-session
|
|
210
|
+
|
|
211
|
+
## Recovery without the original owner
|
|
212
|
+
|
|
213
|
+
- Requirement: The team must carry on when the owner exits, and a promoted session resumed later must find its team again.
|
|
214
|
+
- Design response: When the owner exits, another connected main session takes owner.lock and resumes members by their recorded state: busy members get a message to check their last steps and continue, idle members stay idle, and open questions reappear. A main session resumed from /resume reads its team entry, then connects to the live coordinator or offers to take ownership.
|
|
215
|
+
- Applies to: main-session, coordinator, team-store, member
|
|
216
|
+
|
|
217
|
+
## Member
|
|
218
|
+
|
|
219
|
+
*An agent-launched Pi process with ACSTACK\_AGENT\_ROLE=member and AC\_TEAMS\_\* variables. It controls only its descendants, and its transcript stays under ac-teams/&\#60;team&\#62;/&\#60;member&\#62;/ until promotion.*
|
|
220
|
+
|
|
221
|
+
- Identity: `member`
|
|
222
|
+
- Kind: component
|
|
223
|
+
- Channels: none
|
|
224
|
+
- Source: [member.yaml](member.yaml)
|
|
225
|
+
|
|
226
|
+
## Main-agent signal
|
|
227
|
+
|
|
228
|
+
- Requirement: ACStack's large-read guard applies only to main agents. A main agent is a session the user created or promoted, never one an agent created. The signal must reach in-process Pi hooks and shell hooks, and must change when a member is promoted.
|
|
229
|
+
- Design response: The coordinator sets ACSTACK\_AGENT\_ROLE=member on every member process, next to AC\_TEAMS\_MEMBER. Absent or "main" means main; any other value means not main. Children of a member inherit it. Promotion always relaunches the transcript in a new process without ACSTACK\_AGENT\_ROLE or any AC\_TEAMS\_\* variable, so a live process never needs to change its role. Main status grants no permissions.
|
|
230
|
+
- Applies to: coordinator, member, main-session, read-guard
|
|
231
|
+
|
|
232
|
+
## Separate identity, discovery, membership, and authority
|
|
233
|
+
|
|
234
|
+
- Requirement: Issue \#19 forbids tying session identity or permission authority to picker visibility, storage location, or a single main session.
|
|
235
|
+
- Design response: Identity is the Pi session id. Discovery is where the transcript file lives. Membership and role are records in the team store. Ownership is whoever holds owner.lock. Authority comes from the member's role in the team store, which only explicit user actions change. Moving a file or taking the lock changes nothing else.
|
|
236
|
+
- Applies to: main-session, member, coordinator, team-store, transcripts
|
|
237
|
+
|
|
238
|
+
## Recovery without the original owner
|
|
239
|
+
|
|
240
|
+
- Requirement: The team must carry on when the owner exits, and a promoted session resumed later must find its team again.
|
|
241
|
+
- Design response: When the owner exits, another connected main session takes owner.lock and resumes members by their recorded state: busy members get a message to check their last steps and continue, idle members stay idle, and open questions reappear. A main session resumed from /resume reads its team entry, then connects to the live coordinator or offers to take ownership.
|
|
242
|
+
- Applies to: main-session, coordinator, team-store, member
|
|
243
|
+
|
|
244
|
+
## Coordinator
|
|
245
|
+
|
|
246
|
+
*The team's single referee, running inside whichever main session holds owner.lock. It starts and stops member processes, answers every team\_agent, team\_task, and team\_message call, delivers message notifications, checks who may do what, and runs promotion, joining, and takeover.*
|
|
247
|
+
|
|
248
|
+
- Identity: `coordinator`
|
|
249
|
+
- Kind: component
|
|
250
|
+
- Channels: none
|
|
251
|
+
- Source: [coordinator.yaml](coordinator.yaml)
|
|
252
|
+
|
|
253
|
+
## Main-agent signal
|
|
254
|
+
|
|
255
|
+
- Requirement: ACStack's large-read guard applies only to main agents. A main agent is a session the user created or promoted, never one an agent created. The signal must reach in-process Pi hooks and shell hooks, and must change when a member is promoted.
|
|
256
|
+
- Design response: The coordinator sets ACSTACK\_AGENT\_ROLE=member on every member process, next to AC\_TEAMS\_MEMBER. Absent or "main" means main; any other value means not main. Children of a member inherit it. Promotion always relaunches the transcript in a new process without ACSTACK\_AGENT\_ROLE or any AC\_TEAMS\_\* variable, so a live process never needs to change its role. Main status grants no permissions.
|
|
257
|
+
- Applies to: coordinator, member, main-session, read-guard
|
|
258
|
+
|
|
259
|
+
## Separate identity, discovery, membership, and authority
|
|
260
|
+
|
|
261
|
+
- Requirement: Issue \#19 forbids tying session identity or permission authority to picker visibility, storage location, or a single main session.
|
|
262
|
+
- Design response: Identity is the Pi session id. Discovery is where the transcript file lives. Membership and role are records in the team store. Ownership is whoever holds owner.lock. Authority comes from the member's role in the team store, which only explicit user actions change. Moving a file or taking the lock changes nothing else.
|
|
263
|
+
- Applies to: main-session, member, coordinator, team-store, transcripts
|
|
264
|
+
|
|
265
|
+
## Duplicate prevention
|
|
266
|
+
|
|
267
|
+
- Requirement: A session must not appear twice in a team, run as two processes, or belong to two teams.
|
|
268
|
+
- Design response: The team store keys members by member id and refuses a second record with the same Pi session id. The coordinator accepts one live connection per member and keeps the existing pid and identity check before any launch or relaunch. A session in a team with members cannot join another team.
|
|
269
|
+
- Applies to: coordinator, team-store, main-session
|
|
270
|
+
|
|
271
|
+
## Recovery without the original owner
|
|
272
|
+
|
|
273
|
+
- Requirement: The team must carry on when the owner exits, and a promoted session resumed later must find its team again.
|
|
274
|
+
- Design response: When the owner exits, another connected main session takes owner.lock and resumes members by their recorded state: busy members get a message to check their last steps and continue, idle members stay idle, and open questions reappear. A main session resumed from /resume reads its team entry, then connects to the live coordinator or offers to take ownership.
|
|
275
|
+
- Applies to: main-session, coordinator, team-store, member
|
|
276
|
+
|
|
277
|
+
## Team store
|
|
278
|
+
|
|
279
|
+
*Private team state under \~/.pi/ac-teams/teams/&\#60;team&\#62;/: members with their role, tasks, messages, owner.lock, and each main session's connection token. Sandboxed members cannot read it.*
|
|
280
|
+
|
|
281
|
+
- Identity: `team-store`
|
|
282
|
+
- Kind: store
|
|
283
|
+
- Channels: none
|
|
284
|
+
- Source: [team-store.yaml](team-store.yaml)
|
|
285
|
+
|
|
286
|
+
## Separate identity, discovery, membership, and authority
|
|
287
|
+
|
|
288
|
+
- Requirement: Issue \#19 forbids tying session identity or permission authority to picker visibility, storage location, or a single main session.
|
|
289
|
+
- Design response: Identity is the Pi session id. Discovery is where the transcript file lives. Membership and role are records in the team store. Ownership is whoever holds owner.lock. Authority comes from the member's role in the team store, which only explicit user actions change. Moving a file or taking the lock changes nothing else.
|
|
290
|
+
- Applies to: main-session, member, coordinator, team-store, transcripts
|
|
291
|
+
|
|
292
|
+
## Duplicate prevention
|
|
293
|
+
|
|
294
|
+
- Requirement: A session must not appear twice in a team, run as two processes, or belong to two teams.
|
|
295
|
+
- Design response: The team store keys members by member id and refuses a second record with the same Pi session id. The coordinator accepts one live connection per member and keeps the existing pid and identity check before any launch or relaunch. A session in a team with members cannot join another team.
|
|
296
|
+
- Applies to: coordinator, team-store, main-session
|
|
297
|
+
|
|
298
|
+
## Recovery without the original owner
|
|
299
|
+
|
|
300
|
+
- Requirement: The team must carry on when the owner exits, and a promoted session resumed later must find its team again.
|
|
301
|
+
- Design response: When the owner exits, another connected main session takes owner.lock and resumes members by their recorded state: busy members get a message to check their last steps and continue, idle members stay idle, and open questions reappear. A main session resumed from /resume reads its team entry, then connects to the live coordinator or offers to take ownership.
|
|
302
|
+
- Applies to: main-session, coordinator, team-store, member
|
|
303
|
+
|
|
304
|
+
## Pi transcripts
|
|
305
|
+
|
|
306
|
+
*Standard Pi session files. Main sessions sit at the top level of the project's session folder, where /resume finds them. Members sit under ac-teams/&\#60;team&\#62;/&\#60;member&\#62;/, where only /teams finds them.*
|
|
307
|
+
|
|
308
|
+
- Identity: `transcripts`
|
|
309
|
+
- Kind: store
|
|
310
|
+
- Channels: none
|
|
311
|
+
- Source: [transcripts.yaml](transcripts.yaml)
|
|
312
|
+
|
|
313
|
+
## Separate identity, discovery, membership, and authority
|
|
314
|
+
|
|
315
|
+
- Requirement: Issue \#19 forbids tying session identity or permission authority to picker visibility, storage location, or a single main session.
|
|
316
|
+
- Design response: Identity is the Pi session id. Discovery is where the transcript file lives. Membership and role are records in the team store. Ownership is whoever holds owner.lock. Authority comes from the member's role in the team store, which only explicit user actions change. Moving a file or taking the lock changes nothing else.
|
|
317
|
+
- Applies to: main-session, member, coordinator, team-store, transcripts
|
|
318
|
+
|
|
319
|
+
## Herdr
|
|
320
|
+
|
|
321
|
+
*Optional terminal workspace manager. When present, each main session gets its own tab in the workspace for its worktree.*
|
|
322
|
+
|
|
323
|
+
- Identity: `herdr`
|
|
324
|
+
- Kind: external-system
|
|
325
|
+
- Channels: none
|
|
326
|
+
- Source: [herdr.yaml](herdr.yaml)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
## ACStack read guard
|
|
331
|
+
|
|
332
|
+
*ACStack's large-read hook. It blocks a large untargeted read once in main agents only, and reads ACSTACK\_AGENT\_ROLE on every call to decide.*
|
|
333
|
+
|
|
334
|
+
- Identity: `read-guard`
|
|
335
|
+
- Kind: external-system
|
|
336
|
+
- Channels: none
|
|
337
|
+
- Source: [read-guard.yaml](read-guard.yaml)
|
|
338
|
+
|
|
339
|
+
## Main-agent signal
|
|
340
|
+
|
|
341
|
+
- Requirement: ACStack's large-read guard applies only to main agents. A main agent is a session the user created or promoted, never one an agent created. The signal must reach in-process Pi hooks and shell hooks, and must change when a member is promoted.
|
|
342
|
+
- Design response: The coordinator sets ACSTACK\_AGENT\_ROLE=member on every member process, next to AC\_TEAMS\_MEMBER. Absent or "main" means main; any other value means not main. Children of a member inherit it. Promotion always relaunches the transcript in a new process without ACSTACK\_AGENT\_ROLE or any AC\_TEAMS\_\* variable, so a live process never needs to change its role. Main status grants no permissions.
|
|
343
|
+
- Applies to: coordinator, member, main-session, read-guard
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
id: coordinator
|
|
2
|
+
name: Coordinator
|
|
3
|
+
summary: >-
|
|
4
|
+
The team's single referee, running inside whichever main session holds
|
|
5
|
+
owner.lock. It starts and stops member processes, answers every team_agent,
|
|
6
|
+
team_task, and team_message call, delivers message notifications, checks
|
|
7
|
+
who may do what, and runs promotion, joining, and takeover.
|
|
8
|
+
kind: component
|
|
9
|
+
relationships:
|
|
10
|
+
- id: main-uses-coordinator
|
|
11
|
+
from: main-session
|
|
12
|
+
to: coordinator
|
|
13
|
+
direction: one-way
|
|
14
|
+
label: Hosts as owner, or connects to as a client
|
|
15
|
+
- id: member-uses-coordinator
|
|
16
|
+
from: member
|
|
17
|
+
to: coordinator
|
|
18
|
+
direction: one-way
|
|
19
|
+
label: Calls team tools through
|
|
20
|
+
- id: coordinator-runs-member
|
|
21
|
+
from: coordinator
|
|
22
|
+
to: member
|
|
23
|
+
direction: one-way
|
|
24
|
+
label: Launches, stops, and resumes
|
|
25
|
+
- id: coordinator-launches-main
|
|
26
|
+
from: coordinator
|
|
27
|
+
to: main-session
|
|
28
|
+
direction: one-way
|
|
29
|
+
label: Relaunches promoted members as
|
|
30
|
+
- id: coordinator-writes-store
|
|
31
|
+
from: coordinator
|
|
32
|
+
to: team-store
|
|
33
|
+
direction: one-way
|
|
34
|
+
label: Holds owner.lock and records roles in
|
|
35
|
+
- id: coordinator-moves-transcript
|
|
36
|
+
from: coordinator
|
|
37
|
+
to: transcripts
|
|
38
|
+
direction: one-way
|
|
39
|
+
label: Moves promoted transcripts within
|
|
40
|
+
- id: coordinator-opens-tabs
|
|
41
|
+
from: coordinator
|
|
42
|
+
to: herdr
|
|
43
|
+
direction: one-way
|
|
44
|
+
label: Opens main-session tabs through
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
id: session-roles
|
|
2
|
+
name: Session roles - promotion, joining, and several main sessions
|
|
3
|
+
summary: >-
|
|
4
|
+
A team can have several main sessions. The user can promote a member into a
|
|
5
|
+
main session or join an existing session to a team. Identity, transcript
|
|
6
|
+
discovery, membership, coordinator ownership, and permission authority stay
|
|
7
|
+
separate, and ACSTACK_AGENT_ROLE tells hooks which processes are main.
|
|
8
|
+
detail: journey.md
|
|
9
|
+
elements:
|
|
10
|
+
- user.yaml
|
|
11
|
+
- main-session.yaml
|
|
12
|
+
- member.yaml
|
|
13
|
+
- coordinator.yaml
|
|
14
|
+
- team-store.yaml
|
|
15
|
+
- transcripts.yaml
|
|
16
|
+
- herdr.yaml
|
|
17
|
+
- read-guard.yaml
|
|
18
|
+
concerns:
|
|
19
|
+
- id: main-signal
|
|
20
|
+
name: Main-agent signal
|
|
21
|
+
elements: [coordinator, member, main-session, read-guard]
|
|
22
|
+
requirement: >-
|
|
23
|
+
ACStack's large-read guard applies only to main agents. A main agent is
|
|
24
|
+
a session the user created or promoted, never one an agent created. The
|
|
25
|
+
signal must reach in-process Pi hooks and shell hooks, and must change
|
|
26
|
+
when a member is promoted.
|
|
27
|
+
response: >-
|
|
28
|
+
The coordinator sets ACSTACK_AGENT_ROLE=member on every member process,
|
|
29
|
+
next to AC_TEAMS_MEMBER. Absent or "main" means main; any other value
|
|
30
|
+
means not main. Children of a member inherit it. Promotion always
|
|
31
|
+
relaunches the transcript in a new process without ACSTACK_AGENT_ROLE
|
|
32
|
+
or any AC_TEAMS_* variable, so a live process never needs to change its
|
|
33
|
+
role. Main status grants no permissions.
|
|
34
|
+
- id: separate-axes
|
|
35
|
+
name: Separate identity, discovery, membership, and authority
|
|
36
|
+
elements: [main-session, member, coordinator, team-store, transcripts]
|
|
37
|
+
requirement: >-
|
|
38
|
+
Issue #19 forbids tying session identity or permission authority to
|
|
39
|
+
picker visibility, storage location, or a single main session.
|
|
40
|
+
response: >-
|
|
41
|
+
Identity is the Pi session id. Discovery is where the transcript file
|
|
42
|
+
lives. Membership and role are records in the team store. Ownership is
|
|
43
|
+
whoever holds owner.lock. Authority comes from the member's role in the
|
|
44
|
+
team store, which only explicit user actions change. Moving a file or
|
|
45
|
+
taking the lock changes nothing else.
|
|
46
|
+
- id: duplicate-prevention
|
|
47
|
+
name: Duplicate prevention
|
|
48
|
+
elements: [coordinator, team-store, main-session]
|
|
49
|
+
requirement: >-
|
|
50
|
+
A session must not appear twice in a team, run as two processes, or
|
|
51
|
+
belong to two teams.
|
|
52
|
+
response: >-
|
|
53
|
+
The team store keys members by member id and refuses a second record
|
|
54
|
+
with the same Pi session id. The coordinator accepts one live
|
|
55
|
+
connection per member and keeps the existing pid and identity check
|
|
56
|
+
before any launch or relaunch. A session in a team with members cannot
|
|
57
|
+
join another team.
|
|
58
|
+
- id: recovery
|
|
59
|
+
name: Recovery without the original owner
|
|
60
|
+
elements: [main-session, coordinator, team-store, member]
|
|
61
|
+
requirement: >-
|
|
62
|
+
The team must carry on when the owner exits, and a promoted session
|
|
63
|
+
resumed later must find its team again.
|
|
64
|
+
response: >-
|
|
65
|
+
When the owner exits, another connected main session takes owner.lock
|
|
66
|
+
and resumes members by their recorded state: busy members get a message
|
|
67
|
+
to check their last steps and continue, idle members stay idle, and open
|
|
68
|
+
questions reappear. A main session resumed
|
|
69
|
+
from /resume reads its team entry, then connects to the live
|
|
70
|
+
coordinator or offers to take ownership.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
id: herdr
|
|
2
|
+
name: Herdr
|
|
3
|
+
summary: >-
|
|
4
|
+
Optional terminal workspace manager. When present, each main session gets
|
|
5
|
+
its own tab in the workspace for its worktree.
|
|
6
|
+
kind: external-system
|
|
7
|
+
relationships:
|
|
8
|
+
- id: user-uses-herdr
|
|
9
|
+
from: user
|
|
10
|
+
to: herdr
|
|
11
|
+
direction: one-way
|
|
12
|
+
label: Switches between main sessions in
|
|
13
|
+
- id: coordinator-opens-tabs
|
|
14
|
+
from: coordinator
|
|
15
|
+
to: herdr
|
|
16
|
+
direction: one-way
|
|
17
|
+
label: Opens main-session tabs through
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
## Session roles: promotion, joining, and several main sessions
|
|
2
|
+
|
|
3
|
+
Status: draft for review. Implements nothing yet. Resolves the design questions in [issue #19](https://github.com/ac8318740/ac-teams/issues/19).
|
|
4
|
+
|
|
5
|
+
A team can have several **main sessions**. A main session is a Pi session the user created, joined to a team, or promoted. Every other team process is a **member**. The user can promote a member to a main session from the TUI, or join an existing session to a team with `/teams join`. ac-teams marks every member process with `ACSTACK_AGENT_ROLE=member`, so ACStack's large-read guard applies only to main sessions.
|
|
6
|
+
|
|
7
|
+
### Review questions
|
|
8
|
+
|
|
9
|
+
1. Does promotion by relaunch feel seamless enough, given that a promoted session moves to its own Herdr tab?
|
|
10
|
+
2. Does it look right that every main session can control every member and other main sessions? That matches what an unsandboxed session can already do through its shell.
|
|
11
|
+
3. Is the takeover after the owner exits (automatic, with a notice) the right default?
|
|
12
|
+
4. Are the changes and tests at the end complete enough to start implementation?
|
|
13
|
+
|
|
14
|
+
### Trade-offs to judge
|
|
15
|
+
|
|
16
|
+
- **Relaunch, not live handover.** Promotion stops the member process and reopens its transcript in a new process. The member loses its in-flight turn, but its role, sandbox, token, and environment change cleanly.
|
|
17
|
+
- **No coordinator daemon.** The coordinator stays inside whichever main session holds `owner.lock`. When the owner exits, members stop and the next owner resumes them automatically. The cost is a pause and at most one redone turn per busy member.
|
|
18
|
+
- **Wide main-session authority.** Any main session can stop work started by another. ac-teams does not try to guard against this, because an unsandboxed session can already stop processes through the shell or Herdr.
|
|
19
|
+
|
|
20
|
+
## Definitions
|
|
21
|
+
|
|
22
|
+
These five properties are independent. A change to one never changes another.
|
|
23
|
+
|
|
24
|
+
| Property | Defined by | Changes when |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| Identity | Pi session id in the transcript header | Never |
|
|
27
|
+
| Discovery | Where the transcript file lives | Promotion moves it to the top-level session folder |
|
|
28
|
+
| Membership | A member record in the team store | Join adds one; leave clears its role |
|
|
29
|
+
| Coordinator ownership | Holder of `owner.lock` | The owner exits and another main session takes it |
|
|
30
|
+
| Authority | The record's `role`: `main` or `member` | Only promotion, join, or leave, all user actions |
|
|
31
|
+
|
|
32
|
+
### What the coordinator does
|
|
33
|
+
|
|
34
|
+
The coordinator is ac-teams' in-process server. Today it runs inside Main. In this design, it runs inside whichever main session holds `owner.lock`. In practice it:
|
|
35
|
+
|
|
36
|
+
- **Runs member processes.** Starts each member's Pi, either headless or in tmux, optionally inside a Docker sandbox. It also stops and resumes members, and stops them all when its host session exits
|
|
37
|
+
- **Answers team tools.** Every `team_agent`, `team_task`, and `team_message` call from any session arrives over `coordinator.sock` and is applied here, so concurrent edits are serialized
|
|
38
|
+
- **Keeps team state.** Writes members, tasks, and messages to the team store
|
|
39
|
+
- **Delivers notifications.** Tells each session when new team messages arrive
|
|
40
|
+
- **Enforces rules.** Checks authority, depth and member limits, and sandbox grants, and brokers provider credentials for sandboxed members
|
|
41
|
+
|
|
42
|
+
This design keeps all of that. It adds role checks, connections from main sessions it did not launch, promotion, joining, leaving, and ownership takeover.
|
|
43
|
+
|
|
44
|
+
The team id stays the session id of the session that created the team, even after that session leaves or hands over ownership.
|
|
45
|
+
|
|
46
|
+
### Main-agent signal
|
|
47
|
+
|
|
48
|
+
- The coordinator sets `ACSTACK_AGENT_ROLE=member` on every member process, next to `AC_TEAMS_MEMBER`. That includes the terminal-launch script, which today only passes `AC_TEAMS_*` and `HERDR_ENV`
|
|
49
|
+
- A hook treats the session as main when the variable is absent or `main`, and as not main for any other value
|
|
50
|
+
- Anything a member starts inherits the variable and counts as not main
|
|
51
|
+
- Promotion and every main-session launch must remove `ACSTACK_AGENT_ROLE`, every `AC_TEAMS_*` variable, and `HERDR_ENV=0`. Tests must cover this
|
|
52
|
+
- ACStack reads the variable on every tool call. It detects Claude Code and Codex subagents from `agent_id` in their hook payloads instead. That detection belongs to ACStack, not ac-teams
|
|
53
|
+
- Role grants no permission by itself
|
|
54
|
+
|
|
55
|
+
## User flows
|
|
56
|
+
|
|
57
|
+
### Promote a member
|
|
58
|
+
|
|
59
|
+
1. The user opens a teammate from the agent selector or `/teams` and chooses **Promote to main**
|
|
60
|
+
2. If the member is sandboxed, a confirmation states that the session will run with the user's full access. Declining cancels
|
|
61
|
+
3. The coordinator stops the member, waiting for its process to exit as `stop` does today
|
|
62
|
+
4. The coordinator moves the transcript from `ac-teams/<team>/<member>/` to the project's top-level session folder. It removes the member's config at `~/.pi/ac-teams/agents/<id>` and sets the member's role to `main` without a parent. The member's children stay its descendants
|
|
63
|
+
5. The coordinator writes the session's team entry: a Pi custom entry holding the team id and member id, with no secrets
|
|
64
|
+
6. The coordinator relaunches `pi --session <moved file>` with a clean environment:
|
|
65
|
+
- With Herdr, in a new tab in the workspace for the session's worktree, creating the workspace if needed. The tab gets focus and the session's name
|
|
66
|
+
- Without Herdr, in a tmux session on a per-main-session socket that the owner's `kill-server` cannot reach. The user's screen stays attached, and ac-teams prints the reattach command
|
|
67
|
+
7. The new process reads its team entry, connects to the coordinator with a main-session token from the team store, and appears in `/resume`
|
|
68
|
+
|
|
69
|
+
### Join an existing session to a team
|
|
70
|
+
|
|
71
|
+
1. The user runs `/teams join` in an ordinary Pi session
|
|
72
|
+
2. ac-teams lists this project's teams. The user picks one
|
|
73
|
+
3. ac-teams refuses if the session already belongs to a team with members, or if its session id is already in the chosen team
|
|
74
|
+
4. The coordinator adds a member record with role `main` and writes the team entry
|
|
75
|
+
5. The session receives a short summary of open tasks, owners, and recent messages, with its message cursor at the latest message. It claims or creates work with the normal team tools
|
|
76
|
+
|
|
77
|
+
### Several main sessions
|
|
78
|
+
|
|
79
|
+
- Each main session holds one connection to the coordinator. The owner hosts it in-process; the others connect over `coordinator.sock`
|
|
80
|
+
- Every main session sits at depth 0. Any main session can launch, stop, resume, and clean up any member, and stop or resume another main session
|
|
81
|
+
- Stopping a main session asks it to shut down through its connection. Resuming one relaunches its transcript, as in step 6 of promotion
|
|
82
|
+
- Members keep today's rule: they control only their descendants and never a main session
|
|
83
|
+
- Tasks and messages stay shared. Ownership and revision checks still serialize concurrent edits, because one coordinator applies them all
|
|
84
|
+
|
|
85
|
+
### Leave a team
|
|
86
|
+
|
|
87
|
+
`/teams leave` returns the session's in-progress tasks to todo and clears their ownership. The member record keeps its history but loses its role, so its descendants stay under the other main sessions. The team entry records the departure, so a later `/resume` does not rejoin. Leaving is refused while the session is the only main session and members are still live.
|
|
88
|
+
|
|
89
|
+
## Recovery
|
|
90
|
+
|
|
91
|
+
- **The owner exits while another main session runs.** Before stopping members, the owner records each one's state (busy, idle, or needs input) in the team store. Today, `Coordinator.open` discards that state. A connected main session then takes `owner.lock`, opens the coordinator, and runs the resume pass below. Every main session gets a notice naming the new owner
|
|
92
|
+
- **No main session is running when the owner exits.** Members stay stopped with their recorded state. The next main session that opens the team runs the same resume pass
|
|
93
|
+
- **A promoted session is resumed with no coordinator running.** The extension reads its team entry at startup and offers to take ownership. Accepting opens the coordinator and runs the resume pass. Declining runs the session without team tools
|
|
94
|
+
|
|
95
|
+
### Resume pass
|
|
96
|
+
|
|
97
|
+
| Recorded state | Action |
|
|
98
|
+
|---|---|
|
|
99
|
+
| Busy | Resume and send: "Your process was restarted during a coordinator handover. Check your last steps and continue your current task." |
|
|
100
|
+
| Idle | Resume and leave idle |
|
|
101
|
+
| Needs input | Resume and show **Needs input** again |
|
|
102
|
+
|
|
103
|
+
Pi saves each completed message, so a busy member loses only the reply it was generating and redoes that turn. A command killed mid-run may have half finished. The message tells the member to check before it continues, which reduces that risk but does not remove it, the same as stopping a member today.
|
|
104
|
+
- **A main session crashes.** Its record stays. The coordinator marks it interrupted, and any main session can resume it
|
|
105
|
+
- **Promotion fails partway.** Each step is recorded in the team store before the next starts. On failure, the coordinator rolls back to a stopped member with its original transcript path, and the member can be retried or resumed
|
|
106
|
+
|
|
107
|
+
## Security boundaries
|
|
108
|
+
|
|
109
|
+
- Only the user can promote, join, or leave. These actions have no agent-tool form
|
|
110
|
+
- Promoting a sandboxed member is an explicit escalation, approved in the confirmation step. Without approval, nothing changes
|
|
111
|
+
- Main-session tokens live in the private team store, which sandboxed members cannot read. A member therefore cannot impersonate a main session over the socket
|
|
112
|
+
- Descendant non-escalation stays as it is: a member cannot grant its descendants more access than it has
|
|
113
|
+
- Provider credentials for sandboxed members come from the current owner's Pi login. After a takeover, the new owner brokers them under the same OS user
|
|
114
|
+
|
|
115
|
+
## Changes needed before implementation
|
|
116
|
+
|
|
117
|
+
- **Team store schema 4.** Add `role` to members and migrate `root` to a main record. Replace `actor === "root"` checks with role checks. Store each main session's token
|
|
118
|
+
- **Coordinator.**
|
|
119
|
+
- Accept connections from main sessions that were not launched by the coordinator
|
|
120
|
+
- Add promote, join, leave, and the stop and resume of main sessions
|
|
121
|
+
- Add ownership takeover, and recompute descendant depths after a promotion
|
|
122
|
+
- **Launch environment.** Set `ACSTACK_AGENT_ROLE=member` for every member, including the terminal script's variable filter. Strip it on main-session launches
|
|
123
|
+
- **Extension.** Read the team entry at `session_start`. Add `/teams join`, `/teams leave`, and the **Promote to main** action. Open Herdr tabs, or use the tmux fallback
|
|
124
|
+
- **Docs.** Update `docs/usage.md`, `docs/operations.md`, and `docs/access.md`
|
|
125
|
+
|
|
126
|
+
## Tests needed
|
|
127
|
+
|
|
128
|
+
- A launched member has `ACSTACK_AGENT_ROLE=member`, in both RPC and terminal modes
|
|
129
|
+
- A promoted session's environment has no `ACSTACK_AGENT_ROLE` and no `AC_TEAMS_*` variables
|
|
130
|
+
- Promotion keeps the session id, moves the file, updates the stored path, and writes the team entry. Pi's session listing then includes the session
|
|
131
|
+
- A sandboxed promotion is refused without confirmation and runs unsandboxed after it
|
|
132
|
+
- Joining refuses a duplicate session id, and refuses a session that leads another team with members
|
|
133
|
+
- A main session can stop and resume members and other main sessions. A member cannot act on a main session
|
|
134
|
+
- When the owner exits, another main session takes the lock. Busy members resume with the continue message, idle members stay idle, and members that needed input show **Needs input** again
|
|
135
|
+
- When no main session is running, the next one to open the team runs the same resume pass
|
|
136
|
+
- Resuming a promoted transcript with no coordinator offers takeover, and declining leaves team tools off
|
|
137
|
+
- Leaving returns in-progress tasks to todo and keeps descendants manageable
|
|
138
|
+
- A promotion interrupted after each step rolls back to a resumable member
|
|
139
|
+
- Schema 3 teams migrate to schema 4 with root as a main record
|