ac-teams 0.0.6 → 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 +4 -3
- package/docs/access.md +103 -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/development.md +18 -0
- package/docs/operations.md +18 -7
- package/docs/usage.md +14 -3
- package/package.json +1 -1
- package/src/access.ts +175 -0
- package/src/commands.ts +1 -1
- package/src/config.ts +4 -2
- package/src/coordinator.ts +472 -38
- package/src/egress.ts +187 -0
- package/src/index.ts +253 -58
- package/src/mains.ts +41 -0
- package/src/panel.ts +9 -1
- package/src/sandbox-bridge.mjs +33 -0
- package/src/sandbox.Dockerfile +8 -0
- package/src/schemas.ts +10 -2
- package/src/store.ts +38 -11
- package/src/terminal-agent.ts +1 -1
- package/src/terminal-ui.ts +6 -3
- package/src/types.ts +24 -4
- package/src/ui-types.ts +4 -0
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Persistent agent teams for [Pi](https://github.com/earendil-works/pi): shared ta
|
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
|
-
Requires Linux, Node 22.19 or newer, and an authenticated `pi` executable on `PATH`. Interactive teams also require `tmux` on `PATH`. Tested with Pi 1.4.0.
|
|
7
|
+
Requires Linux, Node 22.19 or newer, and an authenticated `pi` executable on `PATH`. Interactive teams also require `tmux` on `PATH`. Tested with Pi 1.4.0 and 1.5.0.
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
10
|
pi install npm:ac-teams@0.0.4
|
|
@@ -28,13 +28,13 @@ 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
|
|
|
35
35
|
## Know the boundaries
|
|
36
36
|
|
|
37
|
-
- Members share your filesystem access. All members can read all team messages, including direct messages.
|
|
37
|
+
- Members share your filesystem access unless launched with [enforced access](docs/access.md), an opt-in Docker sandbox that needs Docker and a locally built member image. All members can read all team messages, including direct messages.
|
|
38
38
|
- Limits cover only `team_agent`, not other delegation tools. Use separate worktrees for concurrent writers.
|
|
39
39
|
- Stop retains tasks, sessions, and worktrees. Worktree deletion is explicit and requires clean, integrated work.
|
|
40
40
|
- Exit confirmation needs Pi's `session_before_exit` hook, which installed Pi 1.4.0 lacks. Without it, shutdown cleans up but cannot ask first. Signals always bypass confirmation.
|
|
@@ -45,6 +45,7 @@ See the [operations guide](docs/operations.md) for recovery guarantees, storage,
|
|
|
45
45
|
|
|
46
46
|
- [Usage](docs/usage.md): tool actions, task rules, messages, and companion tools
|
|
47
47
|
- [Operations](docs/operations.md): limits, stop/resume, worktrees, and security
|
|
48
|
+
- [Enforced access](docs/access.md): Docker-enforced read/write grants, outbound proxy and destination policy, and setup
|
|
48
49
|
- [Development](docs/development.md): source setup, verification, and publishing
|
|
49
50
|
- [Implementation history](docs/implementation-plan.md): design decisions and prior art
|
|
50
51
|
|
package/docs/access.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Enforce member access
|
|
2
|
+
|
|
3
|
+
By default, members are ordinary unsandboxed Pi processes with your full user permissions. Enforced access is opt-in: ac-teams runs the member's whole Pi process, including its shell, extensions, and subprocesses, in a Docker container that sees only the directories you grant. If enforcement is unavailable, the launch fails; ac-teams never falls back to an unsandboxed member.
|
|
4
|
+
|
|
5
|
+
## Request access
|
|
6
|
+
|
|
7
|
+
`team_agent` launch accepts `access`:
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{ "action": "launch", "name": "reviewer", "brief": "...", "access": { "read": ["."] } }
|
|
11
|
+
{ "action": "launch", "name": "parser", "brief": "...", "cwd": "/repo/parser", "access": { "read": ["/repo/docs", "/repo/lib"], "write": ["."] } }
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
- Paths are directories, absolute or relative to the member's `cwd`. Write implies read. The `cwd` must be inside a grant.
|
|
15
|
+
- `network.allow` limits outbound destinations: `host`, or `*.host` for its subdomains, each optionally `:port`; without a port, an entry allows ports 80 and 443. `[]` allows only the model provider. Omit `allow` for any public destination. Omitting `network` keeps the requester's network access; from an unsandboxed requester that is any public destination.
|
|
16
|
+
- Omitting `access` inherits the requesting member's access. From an unsandboxed requester, it launches an unsandboxed member, as before.
|
|
17
|
+
- Members can only narrow access: a sandboxed member cannot grant a path outside its own access, give write where it has read, allow a destination its own allowlist does not, or launch or resume an unsandboxed member. Access is fixed at launch; resume keeps it.
|
|
18
|
+
- Refused grants: `/`, your home directory or its parents, `/tmp`, `/run`, anything under `/proc`, `/dev`, or `/sys`, and anything containing or inside `~/.pi/agent` or `~/.pi/ac-teams`. They would expose host service sockets, credentials, transcripts, or coordinator state.
|
|
19
|
+
- Grants must be canonical paths with no symlinks, and cannot contain commas, which Docker's mount syntax cannot escape.
|
|
20
|
+
- Docker resolves mount paths when a container starts, so a sandboxed writer could swap a granted path for a symlink just before another member starts. ac-teams holds each grant open from before the container is created, and after the container starts, before any member code runs, compares every mounted grant with the directory it holds; any difference removes the container. A writer of `/repo` and a reader of `/repo/src` can therefore run together. Runtime, transcript, and socket mounts are not compared this way, so none may sit strictly inside another live member's writable grant, across all teams. This protects against sandboxed members only; your own processes, Main, and unsandboxed members can change any path and are trusted.
|
|
21
|
+
- Sandboxed members cannot create or clean up worktrees, send user team actions (`/teams` feedback, task requests, resume), or open other members' terminals; use Main.
|
|
22
|
+
|
|
23
|
+
Before launching, check `sandbox` in `team_agent` `list` or `limits`: `{ "available": true, "backend": "docker" }`, or `available: false` with a `reason`. Every member receives `AC_TEAMS_ACCESS`: `{"enforced":true,"read":[...],"write":[...],"network":{"allow":[...]}}` (no `allow` means any public destination), or `{"enforced":false}`. `team_agent status` reports the coordinator's authoritative `access`.
|
|
24
|
+
|
|
25
|
+
## Write grants and untrusted output
|
|
26
|
+
|
|
27
|
+
A write grant lets the member change those live files, including Git metadata such as hooks and config, Pi project resources, and build scripts. Treat the result like any untrusted contribution: review changes before you run them, commit them, or start Git or Pi in those directories. Running changed code later outside the sandbox is your choice, not a sandbox escape.
|
|
28
|
+
|
|
29
|
+
## What an enforced member can reach
|
|
30
|
+
|
|
31
|
+
| Resource | Enforced member |
|
|
32
|
+
| --- | --- |
|
|
33
|
+
| Granted directories | Mounted at the same paths, read-only or read-write |
|
|
34
|
+
| Rest of the host filesystem | Not mounted. The container's system files come from the member image |
|
|
35
|
+
| Pi runtime | Read-only: the ac-teams package and these entries from your Pi agent directory: `npm`, `extensions`, `themes`, `skills`, `prompts`, `AGENTS.md`, `keybindings.json`, `acstack.json`, `models-store.json`. They run as trusted code inside the container and are not secret-free by design: the member can read your installed package code and lockfile, custom extension source, and global instructions. Keep secrets out of them. Your real `auth.json`, sessions, and caches are not mounted |
|
|
36
|
+
| Web search settings | A copy of your `web-search.json` (including any search API keys) with the proxy set |
|
|
37
|
+
| Trust decisions | A filtered `trust.json` with only the entries for the member's `cwd` and its parents |
|
|
38
|
+
| Settings | A copy of your Pi `settings.json`, including its package list and device ID, so do not store secrets there |
|
|
39
|
+
| Scratch | Private `/tmp` (512 MiB), home (256 MiB), and Pi agent directory (64 MiB), discarded at exit |
|
|
40
|
+
| Network | Outbound HTTP and HTTPS through the parent's proxy only (below); no direct network |
|
|
41
|
+
| Processes | Private process namespace; host processes are invisible |
|
|
42
|
+
| Team | The coordinator socket for team tools, and the member's own transcript directory |
|
|
43
|
+
| Environment | A fixed minimal set; host variables, including credentials, are not inherited |
|
|
44
|
+
|
|
45
|
+
Any Unix socket inside a granted directory remains reachable, so grant narrow directories.
|
|
46
|
+
|
|
47
|
+
Containers run as your user with all capabilities dropped, `no-new-privileges`, a read-only root filesystem, `--network none`, no published ports, no restart policy, no Docker socket, and logging disabled. Before starting a container, ac-teams inspects its effective configuration and refuses to start it on any difference in network, ports, privileges, capabilities, security options, namespaces, devices, mounts, image volumes, user, limits, or restart policy. Docker reports bind sources unresolved, so symlink swaps are caught by re-resolving sources instead. It checks network and ports again after start.
|
|
48
|
+
|
|
49
|
+
## Outbound access goes through the parent's proxy
|
|
50
|
+
|
|
51
|
+
Containers have no network interface except loopback. A bridge inside the container exposes a proxy owned by the root Pi process on `127.0.0.1:3128`, and members get `HTTP_PROXY`, `HTTPS_PROXY`, their lowercase forms, `NO_PROXY` for loopback, and `NODE_USE_ENV_PROXY=1`. Pi, Node, curl, and other clients that honour these variables reach the web; ac-teams also points pi-web-access and billion-context at the proxy through their own settings. Clients that ignore proxy settings have no network at all; there is no transparent capture of other protocols.
|
|
52
|
+
|
|
53
|
+
The proxy:
|
|
54
|
+
|
|
55
|
+
- Handles HTTPS through `CONNECT` and plain HTTP requests, without following redirects; each new request is checked again
|
|
56
|
+
- Resolves names itself and refuses a destination if any resolved address is not global unicast: loopback, private, CGNAT (including Tailscale), link-local and cloud metadata, documentation, multicast, IPv4-mapped and translated forms, and every address of this host, even public ones. It connects to the address it checked, so DNS cannot change in between
|
|
57
|
+
- Applies the member's `network.allow` list, if any, but always allows the selected model provider's endpoint so Pi can run
|
|
58
|
+
- Is reachable only from that member's container, through its own socket
|
|
59
|
+
- Accepts at most 256 open connections per member, so one member cannot exhaust Main's file descriptors
|
|
60
|
+
|
|
61
|
+
A destination allowlist does not keep data private: an allowed host, including the model provider, receives whatever the member sends it.
|
|
62
|
+
|
|
63
|
+
## Model credentials
|
|
64
|
+
|
|
65
|
+
The member gets an `auth.json` with only its selected provider's current OAuth access token and expiry: no refresh token and no other providers. Main's Pi refreshes the token and rewrites the member's copy when it changes. The member can use, or leak, that token until it expires, and spend your quota with it; it cannot mint new tokens. Switching to another provider inside the member fails, because no other credential is present. A member with a `network.allow` list keeps its provider: its children and its resumes must use the same provider, so the always-allowed provider endpoint cannot widen its network access. The file is removed when the member exits; if Main's Pi is killed, it stays until that team is opened again and its orphans are cleaned up.
|
|
66
|
+
|
|
67
|
+
Supported: OAuth `anthropic` and `openai` models at their default endpoints, tested with Pi 1.4.0 and 1.5.0. Other providers, API-key credentials, or a changed credential shape fail the launch.
|
|
68
|
+
|
|
69
|
+
## Resource limits
|
|
70
|
+
|
|
71
|
+
Each member container is capped at 2 GiB of memory (no swap) and 1024 processes. ac-teams allows at most as many enforced members at once, across all teams on the host, as fit in 75% of host memory at that cap (5 on a 15 GiB host). It counts created and running member containers after creating a new one, so concurrent launches cannot both pass. This bounds the sum of container caps; it does not limit Main, unsandboxed members, or other host processes. CPU is not capped.
|
|
72
|
+
|
|
73
|
+
A new enforced member takes about 20 seconds to start, because Pi compiles TypeScript extensions into an empty cache.
|
|
74
|
+
|
|
75
|
+
## Set up Docker
|
|
76
|
+
|
|
77
|
+
Enforced access needs a running Docker daemon your user can use, and the member image for your Pi version. Using Docker means trusting the Docker daemon, which runs as root; membership in the `docker` group is equivalent to root access on the host.
|
|
78
|
+
|
|
79
|
+
When the image is missing, `team_agent list` reports the exact build command, for example:
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
docker build -t ac-teams-member:1.5.0-<hash> --build-arg PI_VERSION=1.5.0 -f <ac-teams>/src/sandbox.Dockerfile <ac-teams>/src
|
|
83
|
+
```
|
|
84
|
+
|
|
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
|
+
|
|
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
|
+
|
|
91
|
+
## Storage
|
|
92
|
+
|
|
93
|
+
- Coordinator state and settings: `~/.pi/ac-teams/teams/<team>/`, `~/.pi/ac-teams/config.json`, and `~/.pi/ac-teams/display.json`
|
|
94
|
+
- Generated member configuration (settings copy, provider access token, trust and web search copies, launch file): `~/.pi/ac-teams/agents/<member>/`, removed when the member exits
|
|
95
|
+
- Member transcripts: `<Main's session directory>/ac-teams/<team>/<member>/`, normally under `~/.pi/agent/sessions/<project>/`. Pi's `/resume` lists only top-level sessions, so member transcripts appear in `/teams`, not `/resume`
|
|
96
|
+
|
|
97
|
+
## Known limits
|
|
98
|
+
|
|
99
|
+
- Tools outside the mounted runtime, such as the ACStack CLI at `~/.claude/acstack/bin/acstack`, need explicit read grants for their directory and any symlink target. Grant those narrow directories, never `~/.claude`, which holds credentials.
|
|
100
|
+
- Interactive members run in the team's tmux pane through `docker start --attach`, on the container's own terminal.
|
|
101
|
+
- Linux only. [Development](development.md#enforced-access-verification) records what has been verified.
|
|
102
|
+
|
|
103
|
+
[Back to README](../README.md)
|
|
@@ -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
|