@awebai/oats 0.22.0 → 0.22.1
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 +30 -47
- package/bin/oats.mjs +11 -6
- package/capabilities/oats-authoring/LICENSE +21 -0
- package/capabilities/oats-authoring/oats-package.json +11 -0
- package/capabilities/oats-authoring/oats.json +4 -4
- package/capabilities/oats-authoring/skills/integration-authoring/SKILL.md +63 -0
- package/capabilities/oats-authoring/skills/skill-craft/SKILL.md +109 -0
- package/capabilities/oats-authoring/skills/soul-craft/SKILL.md +109 -0
- package/capabilities/oats-aweb/oats.json +6 -6
- package/capabilities/oats-aweb/skills/LICENSE +21 -0
- package/capabilities/oats-aweb/skills/VENDORED.md +26 -0
- package/capabilities/oats-aweb/skills/aweb-identity/SKILL.md +201 -0
- package/capabilities/oats-aweb/skills/aweb-messaging/SKILL.md +161 -0
- package/capabilities/oats-aweb/skills/aweb-messaging/references/messaging-scenarios.md +61 -0
- package/capabilities/oats-aweb/skills/aweb-team-membership/SKILL.md +328 -0
- package/capabilities/oats-aweb/skills/aweb-team-membership/references/team-membership-reference.md +74 -0
- package/capabilities/oats-jira/oats.json +1 -1
- package/capabilities/oats-linear/oats.json +1 -1
- package/capabilities/oats-okf/agents/memory-harvest/soul.yaml +6 -0
- package/capabilities/oats-okf/bin/oats-okf.mjs +82 -51
- package/capabilities/oats-okf/oats.json +4 -1
- package/capabilities/oats-review/oats.json +1 -1
- package/docs/2026-09-03-architecture-proposal.md +642 -0
- package/docs/first-team-demo.md +87 -0
- package/docs/first-team.md +179 -0
- package/docs/integrations.md +83 -65
- package/docs/layers.md +356 -80
- package/docs/migration-from-oas.md +80 -116
- package/docs/release-notes/v0.22.1.md +106 -0
- package/lib/core.mjs +255 -363
- package/package.json +1 -1
- package/packages/record/README.md +76 -16
- package/packages/record/docs/turn-record-sot.md +1 -1
- package/packages/record/lib/store.mjs +207 -43
- package/capabilities/oats-aweb/package.json +0 -20
- package/capabilities/oats-jira/package.json +0 -25
- package/capabilities/oats-linear/README.md +0 -234
- package/capabilities/oats-linear/package.json +0 -29
- package/capabilities/oats-linear/test/oats-linear.test.mjs +0 -168
- package/capabilities/oats-okf/package.json +0 -22
- /package/capabilities/oats-okf/agents/{memory-harvest.md → memory-harvest/AGENTS.md} +0 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: aweb-team-membership
|
|
3
|
+
description: This skill should be used when joining or being added to an aweb team, picking the correct invite/add-member path for the team's authority model (hosted vs BYOT), accepting invites, fetching team certificates, switching the active team across multiple memberships, distinguishing hosted from Bring Your Own Team (BYOT) authority, running the fresh BYOT setup into aweb cloud, or diagnosing team-certificate and active-team failures. Use this whenever the question is about WHICH TEAM the agent acts in or how it became a member.
|
|
4
|
+
allowed-tools: "Bash(aw *)"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# aweb Team Membership
|
|
8
|
+
|
|
9
|
+
Use this skill when the question is about teams — joining, leaving, switching between, or troubleshooting team certificates. For the agent's own identity (keypair, `did:key`/`did:aw`, AWID, custody, addressability, inbound mode, contacts, key rotation), load `aweb-identity`. For day-to-day work coordination, load `aweb-coordination`. For mail/chat policy, load `aweb-messaging`. For creating a new team from a template, load `aweb-bootstrap`.
|
|
10
|
+
|
|
11
|
+
## Foundations
|
|
12
|
+
|
|
13
|
+
This skill builds on the identity vocabulary in `aweb-identity` (keypair, `did:key`, `did:aw`, AWID, custodial vs self-custodial). Read that section first if any of those terms are unfamiliar. Team-specific additions:
|
|
14
|
+
|
|
15
|
+
- **Team controller** — a keypair separate from member identities. Its private key signs team certificates; its public key (recorded in AWID) is what verifies whether a certificate is genuine. Team controllers authorize membership; they do not decrypt member conversations and must not substitute a member's E2E encryption key.
|
|
16
|
+
- **Team certificate** — a signed statement that a specific `did:key` is a member of a specific team, with an alias and metadata. Public; replicated in AWID. Stored locally in `.aw/team-certs/*.pem`. A certificate proves team membership; it is not a message-decryption key.
|
|
17
|
+
- **Team id** — canonical form is `<name>:<namespace>` (e.g. `personal:acme.com`, `aweb:juan.aweb.ai`). The name is the team; the namespace is the DNS-backed AWID namespace it lives under.
|
|
18
|
+
- **Hosted vs BYOT team authority** — *hosted* means aweb holds the team controller signing key (for `*.aweb.ai` namespaces). *BYOT* (Bring Your Own Team) means the customer holds the team controller signing key (for their own domain registered in AWID). The customer/team controller is the only party that can add or remove members from a BYOT team; the dashboard never adds a BYOT member directly — it imports/syncs customer-signed facts.
|
|
19
|
+
|
|
20
|
+
## Team-related files in `.aw/`
|
|
21
|
+
|
|
22
|
+
For identity files (`signing.key`, `workspace.yaml` server URL), see `aweb-identity`. Team-specific files:
|
|
23
|
+
|
|
24
|
+
- `teams.yaml` — local index of teams this identity is a member of. Top-level `active_team:` selects which membership is the default for commands run here. `aw team list` reads this; `aw team switch <team-id>` updates it.
|
|
25
|
+
- `team-certs/*.pem` — public team certificates this identity has been issued (one `.pem` per team membership). `teams.yaml` and `workspace.yaml` reference these by `cert_path`.
|
|
26
|
+
|
|
27
|
+
## Custody × Authority matrix
|
|
28
|
+
|
|
29
|
+
Identity custody (where the private key lives) and team authority (who holds the team controller key) are independent axes. Use the matrix to pick the right joining path:
|
|
30
|
+
|
|
31
|
+
| Team authority | Identity custody | Meaning |
|
|
32
|
+
| --- | --- | --- |
|
|
33
|
+
| Hosted | Custodial | aweb manages team authority AND holds hosted identity signing key material (browser/MCP). Messaging in this mode is server-readable hosted messaging, not E2E. |
|
|
34
|
+
| Hosted | Self-custodial | aweb manages team authority; the terminal agent holds its own `.aw/signing.key`. |
|
|
35
|
+
| BYOT | Self-custodial | the customer controls team authority; the agent holds its own key. |
|
|
36
|
+
| BYOT | Custodial | the customer controls team authority; aweb may hold the identity key only after customer-signed BYOT facts authorize it. |
|
|
37
|
+
|
|
38
|
+
A custodial identity has **no BYOT team authority** until the customer-signed team certificate and address facts match. Do not infer team authority from identity custody.
|
|
39
|
+
|
|
40
|
+
For E2E messaging, custody and team membership are still not enough by themselves: the recipient's encryption public key must be identity-authorized as described in `docs/e2e-messaging-contract.md`. Team/namespace authority may distribute that assertion, but it must not replace the member's key. If an encryption-key check fails, do not suggest a team-controller workaround or plaintext fallback; stop and route the user to the approved identity/key setup or recovery flow.
|
|
41
|
+
|
|
42
|
+
## Readiness checks (membership level)
|
|
43
|
+
|
|
44
|
+
Start with:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
aw workspace status
|
|
48
|
+
aw team list
|
|
49
|
+
aw id cert show
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Interpret failures by what's missing (for self-custodial CLI workspaces; custodial browser/MCP identities live entirely in the hosted account):
|
|
53
|
+
|
|
54
|
+
- **`.aw/teams.yaml` missing or empty** — this workspace's identity holds no team memberships at all. Join one (see paths below) before attempting team coordination.
|
|
55
|
+
- **No `.aw/team-certs/<team>.pem` for `teams.yaml`'s active team** — identity exists but holds no cert for the active team. Accept an invite, request a certificate, or switch to a team you have a cert for.
|
|
56
|
+
- **Active team mismatch** — `teams.yaml` lists multiple memberships and `active_team:` selects the default; commands route to that team unless `--team <team-id>` overrides for a single invocation. If commands appear to land in the wrong team, fix `active_team:` (run `aw team switch <team-id>`).
|
|
57
|
+
- **Workspace not bound to a server** — `workspace.yaml` missing means there's no aweb server to authenticate the certificate against (see `aweb-identity`).
|
|
58
|
+
|
|
59
|
+
## Joining a team — match the path to team authority
|
|
60
|
+
|
|
61
|
+
The right joining path depends entirely on **who holds the team controller signing key**. Pick by authority, not by the word "invite" alone.
|
|
62
|
+
|
|
63
|
+
### Hosted teams (aweb holds the team controller key)
|
|
64
|
+
|
|
65
|
+
Three distinct paths exist; they are NOT interchangeable.
|
|
66
|
+
|
|
67
|
+
**Path 1 — Fresh identity at init time.** A new agent without any prior identity arrives at a hosted team via OAuth (browser/MCP) or team API-key (CLI):
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
AWEB_API_KEY=<team-api-key> aw init
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The hosted service provisions the identity and a team certificate together. Browser/MCP harnesses do this through OAuth without a CLI. The result: workspace is initialized, identity is created, team membership is in place. Verify with `aw workspace status` and `aw team list`.
|
|
74
|
+
|
|
75
|
+
**Path 2 — Existing global identity → "Add existing identity" in the dashboard.** When an agent already has a `did:aw` registered in AWID and wants to join an existing hosted team:
|
|
76
|
+
|
|
77
|
+
In the app.aweb.ai dashboard, an owner/admin clicks "Add existing identity" on the team, supplies the global identity's address or `did:aw`, and the backend mints a team certificate with the cloud-held team controller key, registers it in AWID, and prints commands like:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
aw id team fetch-cert --namespace <namespace> --team <team> --cert-id <cert-id>
|
|
81
|
+
aw team switch <team>:<namespace>
|
|
82
|
+
aw init # if the joining directory still needs server binding
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
No token round-trip. Direct controller-mint. Available only for hosted teams because BYOT controller keys aren't held by aweb.
|
|
86
|
+
|
|
87
|
+
**Path 3 — CLI invite-token, for hosted self-custodial local or global identities.** Hosted CLI invite tokens are redeemed through the cloud, but the accepting directory keeps its own signing key. Use the default form when the owner wants to invite a new local-workspace identity by token:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# Owner side (in a workspace with the necessary authority):
|
|
91
|
+
aw team invite # local-workspace member token
|
|
92
|
+
|
|
93
|
+
# share the printed <token>
|
|
94
|
+
|
|
95
|
+
# Joiner side (in a clean target directory):
|
|
96
|
+
aw team join <token> --name <name>
|
|
97
|
+
aw init # finish wiring the new workspace if instructed
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`accept-invite` refuses to overwrite an existing `.aw/` identity and generates a fresh local self-custodial identity in the target directory before requesting the certificate. For a hosted global identity, accept the hosted token with `--address <domain>/<name>`:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
aw team join <token> --address <domain>/<name>
|
|
104
|
+
aw init # finish wiring the new workspace if instructed
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
In the hosted `--address` case, the CLI creates a fresh self-custodial global identity for the address, registers it through the hosted service, and installs the hosted team certificate. The hosted service signs the team certificate; it does not receive the accepting directory's private signing key. If the user already has a global identity and only needs a certificate for an existing hosted team, Path 2 (dashboard Add existing identity + `fetch-cert`) remains valid.
|
|
108
|
+
|
|
109
|
+
This is **not** the cross-machine BYOT path. Do not present it as the normal way to join a BYOT team from another machine.
|
|
110
|
+
|
|
111
|
+
**Do not run `aw id team add-member` for a hosted team** — `add-member` signs a certificate with the team controller key, which you do not hold for hosted teams. The CLI will error and direct you to the dashboard "Add existing identity" flow.
|
|
112
|
+
|
|
113
|
+
### BYOT teams (customer holds the team controller key)
|
|
114
|
+
|
|
115
|
+
The dashboard cannot add BYOT members directly. The customer's team controller must sign. Three cases:
|
|
116
|
+
|
|
117
|
+
**Case 1 — Self-custodial identity, cross-machine join.** The joining machine doesn't hold the team controller key:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
# Joining identity machine
|
|
121
|
+
aw id team request --team <team>:<namespace> --name <name>
|
|
122
|
+
|
|
123
|
+
# Controller machine — runs the exact command the request printed:
|
|
124
|
+
aw id team add-member ...
|
|
125
|
+
|
|
126
|
+
# Back on the joining identity machine
|
|
127
|
+
aw id team fetch-cert --namespace <namespace> --team <team> --cert-id <id>
|
|
128
|
+
aw team switch <team>:<namespace>
|
|
129
|
+
aw init # if needed
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The team controller private key never leaves the controller machine.
|
|
133
|
+
|
|
134
|
+
**Case 2 — Custodial browser identity into a BYOT team.** Start from the dashboard's "Create custodial request" action. The dashboard prints the controller-side command block (member identity creation, namespace address assignment, team `add-member`). The team controller runs that block on their machine, then syncs the signed team state into aweb cloud:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
aw id team import-request --team <team> --namespace <namespace> --cloud-team-id <cloud-team-id> --apply
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
aweb cloud projects the customer-signed facts; it does not mint anything itself. For roster changes (add or remove members), the customer controller modifies signed team state and runs `import-request --apply` again to sync.
|
|
141
|
+
|
|
142
|
+
**Case 3 — Same-machine local-controller invite-token convenience.** When the team controller key is on the same machine you're inviting from, you can use the invite-token flow as a shortcut. Two variants:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
# Owner side, same machine, local-controller key present:
|
|
146
|
+
aw team invite # local-workspace member (default)
|
|
147
|
+
aw team invite --global # global-member token (requires existing global identity in the accepting directory)
|
|
148
|
+
|
|
149
|
+
# Joiner side (still same machine, different directory):
|
|
150
|
+
|
|
151
|
+
# For a local invite:
|
|
152
|
+
aw team join <token> --name <name>
|
|
153
|
+
|
|
154
|
+
# For a global invite, the accepting directory must already have a global identity:
|
|
155
|
+
aw id create --domain <domain> --name <name>
|
|
156
|
+
aw team join <token> --address <namespace>/<name>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
For the `--global` case, `accept-invite` does NOT create the global identity — it errors with `no identity found; run aw id create first, or use --local invite` if no identity is present. `--address` selects the registered address to place in the persistent team certificate; the address must resolve to the accepting identity's `did:aw`/`did:key`.
|
|
160
|
+
|
|
161
|
+
This is the local-controller convenience case only. For cross-machine BYOT joins, use Case 1.
|
|
162
|
+
|
|
163
|
+
### Not a membership path: human dashboard invites
|
|
164
|
+
|
|
165
|
+
`/api/v1/teams/.../invite` in the dashboard sends email invitations for **human users** to join the team's dashboard view (with a dashboard role like Owner/Admin/Member). That is independent of AWID agent membership certificates. Do not mix: human dashboard invites do not create agent team-certs and vice versa.
|
|
166
|
+
|
|
167
|
+
## Accepting an invite vs fetching a certificate
|
|
168
|
+
|
|
169
|
+
Two distinct local actions install a membership:
|
|
170
|
+
|
|
171
|
+
- **`aw team join <token>`** (human-facing alias for `aw id team accept-invite <token>`) — redeems a CLI invite token. For hosted invites (Path 3), generates a fresh self-custodial identity in the current directory (refusing to overwrite) and installs the certificate; default is local, while `--address <domain>/<name>` creates/registers a fresh global identity through the hosted service before certificate install. For local-controller same-machine invites (BYOT Case 3), local-member behaves the same way as hosted local; global accepts require an existing global identity (from `aw id create`) and attach a team certificate to it via `--address <namespace>/<name>`.
|
|
172
|
+
- **`aw id team fetch-cert --namespace <namespace> --team <team> --cert-id <id>`** — installs a certificate that has already been minted server-side (by hosted "Add existing identity") or signed by a controller (BYOT `add-member`). Used for hosted Path 2 and BYOT Case 1.
|
|
173
|
+
|
|
174
|
+
If you have a token, use `aw team join` (or the underlying primitive `aw id team accept-invite`). If you have a `cert-id` printed by the dashboard or controller, use `fetch-cert`.
|
|
175
|
+
|
|
176
|
+
## Multiple team memberships
|
|
177
|
+
|
|
178
|
+
One identity can hold multiple team certificates simultaneously — one per team — all stored in `.aw/team-certs/`. Which one is in effect for a given command — and therefore which team's coordination state the command reaches — comes from either the `active_team:` selection in `.aw/teams.yaml` or a per-command `--team <team-id>` argument that overrides it for that one invocation.
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
aw team list # see memberships
|
|
182
|
+
aw team switch <team>:<namespace> # update teams.yaml's active_team
|
|
183
|
+
aw <verb> --team <team>:<namespace> ... # one-off override for team-scoped commands
|
|
184
|
+
aw team leave <team>:<namespace> # remove a local membership
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Acting in the wrong active team can send messages, claims, or locks to the wrong coordination boundary. Switch persistently only when the workspace's ongoing work should move; otherwise use the per-command override.
|
|
188
|
+
|
|
189
|
+
If the recipient's `inbound_mode` is `team-and-contacts`, valid same-team membership is one of the authorization paths for delivery to them. The full inbound-mode model is in `aweb-identity`.
|
|
190
|
+
|
|
191
|
+
## Fresh BYOT setup into aweb cloud
|
|
192
|
+
|
|
193
|
+
Use this flow when the user controls DNS for a domain and wants to create a customer-controlled AWID team, add agents, and import/sync it into app.aweb.ai.
|
|
194
|
+
|
|
195
|
+
Vocabulary:
|
|
196
|
+
|
|
197
|
+
- The namespace is the domain, e.g. `juanreyero.com`.
|
|
198
|
+
- The team is named inside that namespace, e.g. `personal`; its AWID team id is `personal:juanreyero.com`.
|
|
199
|
+
- Agents have addresses under the namespace, e.g. `juanreyero.com/alpha`.
|
|
200
|
+
- Do not call `personal:juanreyero.com` an agent; it is the team id.
|
|
201
|
+
|
|
202
|
+
Before starting, confirm `aw version` includes `aw id namespace prepare-controller` and `aw id namespace check-txt`; older `aw` versions make this flow hard to drive from non-interactive harnesses.
|
|
203
|
+
|
|
204
|
+
**Use `aw id create`, NOT `aw init --byod --global`, for the identity-prep commands in this section.** `aw init --byod --global` is workspace onboarding: it bootstraps the directory and connects it to the `default:<domain>` team on app.aweb.ai (the team created during BYOD onboarding), writing `workspace.yaml`, joining that team, and minting a team certificate. That short-circuits the controller-signed team-state import this section is about. `aw id create` only mints the identity in AWID and writes `.aw/identity.yaml` + `.aw/signing.key`, leaving the team membership for the customer controller to add and sign. If a user already ran the wrong command and needs to recover, see the matching diagnostic bullet in `aweb-identity`'s readiness checks.
|
|
205
|
+
|
|
206
|
+
Namespace controller setup (does not create an identity or team):
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
aw id namespace prepare-controller --domain <domain>
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Pause and have the human add the printed `_awid.<domain>` TXT record. Do not invent DNS values. Tell the human to back up `~/.awid` now; it contains the namespace controller key. After DNS propagates, verify it:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
aw id namespace check-txt --domain <domain>
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Then create the BYOT team with the namespace controller key:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
aw id team create --namespace <domain> --name <team> --display-name "<display name>"
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
After team creation, tell the human to back up `~/.awid` again; it now also contains the team controller key under `~/.awid/team-keys/<domain>/<team>.key`.
|
|
225
|
+
|
|
226
|
+
Add initial global agents:
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
aw id create --domain <domain> --name alpha
|
|
230
|
+
aw id team add-member --team <team> --namespace <domain> --did <alpha_did_key> --name alpha --global --did-aw <alpha_did_aw>
|
|
231
|
+
|
|
232
|
+
aw id create --domain <domain> --name beta
|
|
233
|
+
aw id team add-member --team <team> --namespace <domain> --did <beta_did_key> --name beta --global --did-aw <beta_did_aw>
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Use the actual `did`/`did_aw` values printed by `aw id create`. Do not guess them.
|
|
237
|
+
|
|
238
|
+
Register with aweb cloud without using the dashboard:
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
aw id team register --service https://app.aweb.ai --team <team>:<domain>
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
This signs a service-registration request with the team controller key. It creates or syncs an aweb projection of the AWID team, but it does not upload controller private keys, create identities, or initialize any agent workspace. Read the returned next steps and run the required workspace connection command from each already-certified agent directory:
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
aw workspace connect --service https://app.aweb.ai --team <team>:<domain>
|
|
248
|
+
# equivalent service-oriented primitive: aw service init --service https://app.aweb.ai --team <team>:<domain>
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
`aw workspace connect` / `aw service init` requires the local `.aw/signing.key`, `.aw/teams.yaml`, and `.aw/team-certs/*.pem` for that agent. If the certificate is not installed yet, fetch it first with the certificate id returned by `aw id team add-member`:
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
aw id team fetch-cert --namespace <domain> --team <team> --cert-id <cert-id>
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
After at least one workspace is initialized, the service may suggest a human-claim command such as:
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
aw claim-human --email you@example.com
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
`claim-human` is a service-account/human-login step for billing and dashboard ownership. It is not AWID team-controller authority and it does not add members to the BYOT team.
|
|
264
|
+
|
|
265
|
+
Import into an existing aweb organization:
|
|
266
|
+
|
|
267
|
+
1. In app.aweb.ai, create or select the owner organization that should contain the imported team.
|
|
268
|
+
2. Open the BYOT import flow. Prefer the command shown by the dashboard because it contains the correct `--organization-id`.
|
|
269
|
+
3. First preview:
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
aw id team import-request --team <team> --namespace <domain> --organization-id <org-id>
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Paste the signed output and use Preview.
|
|
276
|
+
|
|
277
|
+
4. If the preview is correct, regenerate an apply request:
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
aw id team import-request --team <team> --namespace <domain> --organization-id <org-id> --apply
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Paste it and use Import / sync.
|
|
284
|
+
|
|
285
|
+
Sync later changes:
|
|
286
|
+
|
|
287
|
+
- After the team exists in aweb cloud, use `--cloud-team-id <cloud-team-id>` instead of `--organization-id`.
|
|
288
|
+
- The dashboard Connect / Sync page should show the exact command. Prefer that command.
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
aw id team import-request --team <team> --namespace <domain> --cloud-team-id <cloud-team-id> --apply
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Diagnostic recipes
|
|
295
|
+
|
|
296
|
+
### "I am in two teams; what does that entail?"
|
|
297
|
+
|
|
298
|
+
Treat teams as separate coordination boundaries for tasks, locks, roles, instructions, presence, and same-team aliases. Global mail/chat first contact uses explicit address routes (`<namespace>/<alias>`); continuations reuse the route already recorded for that conversation/participant. Confirm `active_team:` in `teams.yaml` before relying on local aliases, claiming work, or choosing sender context — or use `--team <team-id>` for a one-off override.
|
|
299
|
+
|
|
300
|
+
### "Team cert or active team mismatch"
|
|
301
|
+
|
|
302
|
+
Inspect `.aw/teams.yaml` and `.aw/team-certs/`. Confirm:
|
|
303
|
+
- a `.pem` file exists for each team you expect to be a member of;
|
|
304
|
+
- `teams.yaml`'s `active_team:` matches the team you actually want commands to land in;
|
|
305
|
+
- `aw team list` agrees with both.
|
|
306
|
+
|
|
307
|
+
Switch with `aw team switch <team-id>`, or reinitialize only after confirming with the team owner/coordinator.
|
|
308
|
+
|
|
309
|
+
### "X says they cannot reach me"
|
|
310
|
+
|
|
311
|
+
First check the route + inbound mode on the recipient side — full model in `aweb-identity`. Then, for `team-and-contacts` recipients, check shared team membership:
|
|
312
|
+
|
|
313
|
+
1. `aw team list` on both sides — do you have a certificate in a common team?
|
|
314
|
+
2. `.aw/team-certs/` — is the certificate present for that team?
|
|
315
|
+
3. Whether the recipient considers your team certificate current (a rotated key on your side requires a re-issued certificate; see `aweb-identity` for rotation).
|
|
316
|
+
|
|
317
|
+
### "I joined a team but commands hit the wrong one"
|
|
318
|
+
|
|
319
|
+
The new membership added a `.pem` to `team-certs/` and a row in `teams.yaml`, but `active_team:` may not have updated. Run `aw team switch <new-team-id>` to update the default, or pass `--team <new-team-id>` to specific commands.
|
|
320
|
+
|
|
321
|
+
## References
|
|
322
|
+
|
|
323
|
+
Read these only when deeper context is needed:
|
|
324
|
+
|
|
325
|
+
- `references/team-membership-reference.md`: detailed hosted/BYOT and diagnostic notes.
|
|
326
|
+
- <https://aweb.ai/docs/teams/>: team model.
|
|
327
|
+
- <https://github.com/awebai/aweb/blob/main/docs/byot-onboarding-contract.md>: fully hosted vs BYOT contract.
|
|
328
|
+
- <https://aweb.ai/docs/agent-guide/>: full agent guide.
|
package/capabilities/oats-aweb/skills/aweb-team-membership/references/team-membership-reference.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# aweb Team Membership Reference
|
|
2
|
+
|
|
3
|
+
## Authority layers
|
|
4
|
+
|
|
5
|
+
- **Namespace authority** controls addresses under a DNS-backed namespace.
|
|
6
|
+
- **Team authority** controls team membership certificates.
|
|
7
|
+
- **Identity custody** controls who holds an agent's signing key.
|
|
8
|
+
- **Workspace binding** controls which local directory acts in which team/server.
|
|
9
|
+
|
|
10
|
+
These layers can combine in multiple ways. Do not assume one from another. The compact custody matrix now lives in the main `SKILL.md` body because it is central to customer comprehension.
|
|
11
|
+
|
|
12
|
+
## Fully Hosted
|
|
13
|
+
|
|
14
|
+
Fully Hosted means aweb operates namespace and team authority for hosted domains such as `*.aweb.ai`. It can mint hosted team certificates and provide simple onboarding. This is the simple default for most users.
|
|
15
|
+
|
|
16
|
+
Hosted OAuth/MCP flows provision custodial addressed/global identities, personal team membership, and harness credentials before a local CLI workspace exists. Team API-key CLI bootstrap is different: it creates a local self-custodial CLI workspace in a hosted team. In OAuth/MCP flows, use CLI checks for diagnosis only when a local workspace is actually involved; do not force BYOT setup.
|
|
17
|
+
|
|
18
|
+
## BYOT
|
|
19
|
+
|
|
20
|
+
BYOT means Bring Your Own Team. It includes older BYOD/BYOIDT terms.
|
|
21
|
+
|
|
22
|
+
In BYOT, the customer controls the DNS namespace controller and team controller. aweb imports customer-signed facts; it does not receive private controller keys.
|
|
23
|
+
|
|
24
|
+
Key command surfaces:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
aw id namespace prepare-controller --domain <domain>
|
|
28
|
+
aw id namespace check-txt --domain <domain>
|
|
29
|
+
aw id create --name <name> --domain <domain>
|
|
30
|
+
aw id team create --namespace <namespace> --name <team>
|
|
31
|
+
aw id team request --team <team>:<namespace> --name <name>
|
|
32
|
+
aw id team add-member --team <team> --namespace <namespace> ...
|
|
33
|
+
aw id team fetch-cert --team <team> --namespace <namespace> --cert-id <id>
|
|
34
|
+
aw id team import-request --namespace <domain> --team <team> --organization-id <org>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Use current `aw ... --help` for exact flags. Treat `aw id namespace prepare-controller` as namespace-authority setup, not identity creation. Treat `aw id team add-member` as a controller-side operation; the joining machine commonly runs `request` and `fetch-cert` only.
|
|
38
|
+
|
|
39
|
+
For the dashboard import/sync path:
|
|
40
|
+
|
|
41
|
+
- Use `--organization-id <org-id>` only for the first import into an owner organization.
|
|
42
|
+
- Use `--cloud-team-id <cloud-team-id>` for later syncs of an already-imported team.
|
|
43
|
+
- Omit `--apply` for preview; add `--apply` only after the preview is correct.
|
|
44
|
+
- The dashboard's Connect / Sync page should show the exact command for the current team. Prefer that command over reconstructing IDs by hand.
|
|
45
|
+
|
|
46
|
+
## Addressability, inbound mode, and contacts
|
|
47
|
+
|
|
48
|
+
Addressability and delivery authorization are separate:
|
|
49
|
+
|
|
50
|
+
- First contact uses a concrete address route (`domain/alias`).
|
|
51
|
+
- `did:aw` is identity binding, not a first-contact delivery route.
|
|
52
|
+
- `inbound_mode=open|team_and_contacts` controls delivery after route validation.
|
|
53
|
+
- `team_and_contacts` accepts verified same-team senders plus exact active identity contacts for trusted non-team senders. Contacts do not create routes or resolver visibility.
|
|
54
|
+
- Reachability fields that appear in support or migration output are compatibility/audit state, not live delivery authority.
|
|
55
|
+
- `aw contacts ...` manages saved contact relationships.
|
|
56
|
+
- `aw id namespace resolve <domain>/<alias> --json` performs a workspace-free directory lookup.
|
|
57
|
+
|
|
58
|
+
## Multi-team safety checklist
|
|
59
|
+
|
|
60
|
+
Before acting in a multi-team identity:
|
|
61
|
+
|
|
62
|
+
1. Run `aw workspace status`.
|
|
63
|
+
2. Confirm active team.
|
|
64
|
+
3. Confirm server URL.
|
|
65
|
+
4. Confirm recipient address belongs to intended team/context.
|
|
66
|
+
5. Use `--team` only for deliberate one-off overrides.
|
|
67
|
+
|
|
68
|
+
## Fail-closed BYOT posture
|
|
69
|
+
|
|
70
|
+
For BYOT imports, fail closed on stale timestamps, invalid signatures, mismatched team IDs, hosted-controller teams, managed hosted namespaces, or custodial identity mismatches.
|
|
71
|
+
|
|
72
|
+
## Key rotation notes
|
|
73
|
+
|
|
74
|
+
Self-custodial rotation depends on access to the existing local signing key. Custodial recovery depends on hosted account recovery. If compromise is suspected, pause sensitive actions and coordinate the new trusted identity/key state with the team.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"capability": "oats.jira",
|
|
3
3
|
"command": "jira",
|
|
4
4
|
"version": "1.0.0",
|
|
5
|
-
"compatibility": { "oats": ">=0.
|
|
5
|
+
"compatibility": { "oats": ">=0.19.0" },
|
|
6
6
|
"layer": "tasks",
|
|
7
7
|
"description": "Tasks layer via Jira: acli-based epic/story/task protocol, agent roster in epic descriptions, label-based agent identity.",
|
|
8
8
|
"requires": [
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"capability": "oats.linear",
|
|
3
3
|
"command": "linear",
|
|
4
4
|
"version": "1.0.0",
|
|
5
|
-
"compatibility": { "oats": ">=0.
|
|
5
|
+
"compatibility": { "oats": ">=0.19.0" },
|
|
6
6
|
"layer": "tasks",
|
|
7
7
|
"description": "Tasks layer via Linear: JSON-first GraphQL commands, project/issue/sub-issue workflow, label-based agent identity.",
|
|
8
8
|
"requires": [],
|
|
@@ -20,24 +20,10 @@
|
|
|
20
20
|
* OATS_TASK (spawn), OATS_REPO/OATS_BRANCH/OATS_WORK (spawn), OATS_META (retire).
|
|
21
21
|
* Output: JSON { meta, brief, warning } on stdout. Failures warn, never block.
|
|
22
22
|
*/
|
|
23
|
-
import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync,
|
|
23
|
+
import { existsSync, mkdirSync, mkdtempSync, writeFileSync, readFileSync, readdirSync, realpathSync, rmSync } from "node:fs";
|
|
24
24
|
import { join, isAbsolute, dirname } from "node:path";
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
|
|
28
|
-
/** The kernel install root. When this package runs from inside the kernel
|
|
29
|
-
* (marketplace source tree), ../../.. works; when it runs as a copied
|
|
30
|
-
* marketplace install (.agents/capabilities/installed/oats-okf), resolve the
|
|
31
|
-
* kernel through `oats root` — the same mechanism adapters use. */
|
|
32
|
-
const FRAMEWORK_ROOT = (() => {
|
|
33
|
-
const rel = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
|
|
34
|
-
if (existsSync(join(rel, "lib", "core.mjs"))) return rel;
|
|
35
|
-
try {
|
|
36
|
-
const root = execSync("oats root", { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 15000 }).trim();
|
|
37
|
-
if (root && existsSync(join(root, "lib", "core.mjs"))) return root;
|
|
38
|
-
} catch { /* fall through */ }
|
|
39
|
-
return rel; // callers report the missing module with a clear path
|
|
40
|
-
})();
|
|
25
|
+
import { tmpdir } from "node:os";
|
|
26
|
+
import { execFile } from "node:child_process";
|
|
41
27
|
|
|
42
28
|
const out = (o) => { process.stdout.write(JSON.stringify(o) + "\n"); process.exit(0); };
|
|
43
29
|
const warn = (m) => out({ warning: `oats-okf: ${String(m).slice(0, 300)}` });
|
|
@@ -67,6 +53,65 @@ catch (e) {
|
|
|
67
53
|
* work; default gpt-5.5, overridable via okf settings { "harvest-model": ... }. */
|
|
68
54
|
const DEFAULT_HARVEST_MODEL = "github-copilot/gpt-5.5";
|
|
69
55
|
|
|
56
|
+
function runtimeError(code, message) {
|
|
57
|
+
return Object.assign(new Error(message), { code });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Canonical package-runtime binary supplied by capability command dispatch.
|
|
61
|
+
* Never discover or resolve the kernel through PATH. */
|
|
62
|
+
function packageRuntimeCli() {
|
|
63
|
+
const cli = process.env.OATS_CLI_BIN;
|
|
64
|
+
if (!cli) throw runtimeError("E_SPAWN_FAILED", "OATS_CLI_BIN is required by the package-runtime contract");
|
|
65
|
+
if (!isAbsolute(cli)) throw runtimeError("E_SPAWN_FAILED", "OATS_CLI_BIN must be an absolute path");
|
|
66
|
+
return cli;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Invoke the versioned package-runtime boundary. Task text crosses the
|
|
70
|
+
* process boundary only through an owner-readable tempfile, removed on every
|
|
71
|
+
* success/failure outcome. */
|
|
72
|
+
async function spawnHarvester(spawnArgs, task) {
|
|
73
|
+
const temp = mkdtempSync(join(tmpdir(), "oats-okf-harvest-"));
|
|
74
|
+
const taskFile = join(temp, "TASK.md");
|
|
75
|
+
try {
|
|
76
|
+
writeFileSync(taskFile, task, { mode: 0o600, flag: "wx" });
|
|
77
|
+
const args = ["spawn", "memory-harvest", ...spawnArgs, "--task-file", taskFile, "--json"];
|
|
78
|
+
const child = await new Promise((resolveChild) => {
|
|
79
|
+
execFile(packageRuntimeCli(), args, {
|
|
80
|
+
encoding: "utf8",
|
|
81
|
+
env: process.env,
|
|
82
|
+
timeout: 300000,
|
|
83
|
+
maxBuffer: 1024 * 1024,
|
|
84
|
+
}, (error, stdout, stderr) => resolveChild({ error, stdout, stderr }));
|
|
85
|
+
});
|
|
86
|
+
if (child.stderr) process.stderr.write(child.stderr);
|
|
87
|
+
if (child.error && !String(child.stdout || "").trim()) {
|
|
88
|
+
throw runtimeError("E_SPAWN_FAILED", child.error.message || child.error);
|
|
89
|
+
}
|
|
90
|
+
let envelope;
|
|
91
|
+
try { envelope = JSON.parse(String(child.stdout || "").trim()); }
|
|
92
|
+
catch { throw runtimeError("E_SPAWN_FAILED", "oats spawn returned an invalid JSON envelope"); }
|
|
93
|
+
if (envelope?.schemaVersion !== 1 || typeof envelope.ok !== "boolean") {
|
|
94
|
+
throw runtimeError("E_SPAWN_FAILED", "oats spawn returned an unsupported JSON envelope");
|
|
95
|
+
}
|
|
96
|
+
if (!envelope.ok) {
|
|
97
|
+
throw runtimeError(envelope.error?.code || "E_SPAWN_FAILED", envelope.error?.message || "oats spawn failed");
|
|
98
|
+
}
|
|
99
|
+
if (child.error) throw runtimeError("E_SPAWN_FAILED", child.error.message || "oats spawn failed");
|
|
100
|
+
if (!envelope.result?.instance) throw runtimeError("E_SPAWN_FAILED", "oats spawn success envelope has no instance");
|
|
101
|
+
return envelope.result;
|
|
102
|
+
} finally {
|
|
103
|
+
rmSync(temp, { recursive: true, force: true });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function harvestSpawnArgs({ slug, parent, repo, work, workDir, branch, model }) {
|
|
108
|
+
const args = ["--purpose", slug, "--parent", parent, "--repo", repo, "--work", work];
|
|
109
|
+
if (workDir) args.push("--work-dir", workDir);
|
|
110
|
+
if (branch) args.push("--branch", branch);
|
|
111
|
+
args.push("--model", model);
|
|
112
|
+
return args;
|
|
113
|
+
}
|
|
114
|
+
|
|
70
115
|
/** Append a one-line entry to an OKF log.md (newest-first, date-grouped per spec §7). */
|
|
71
116
|
function appendLogEntry(logPath, entry, title) {
|
|
72
117
|
const today = new Date().toISOString().slice(0, 10);
|
|
@@ -198,32 +243,20 @@ _(the single next action — keep this current; a fresh session on any model res
|
|
|
198
243
|
if (notes.length === 0) skip("no pending notes");
|
|
199
244
|
if (!root || (!existsSync(root) && !existsSync(join(dirname(root), "local-agents")))) skip("no agents root found above this home");
|
|
200
245
|
if (!inst) skip("no instance identity (run from an instance home)");
|
|
201
|
-
|
|
246
|
+
if (!context) skip("no repository context (instance metadata has no repo)");
|
|
202
247
|
const slug = String(inst).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 30);
|
|
203
248
|
// Debounce: one harvester per source instance at a time (canonical sibling
|
|
204
|
-
// local-agents/ plus legacy nested locations).
|
|
249
|
+
// local-agents/ plus legacy nested locations). The public spawn boundary
|
|
250
|
+
// derives the deterministic instance name from --purpose <slug>.
|
|
205
251
|
const harvesterHomes = [
|
|
206
252
|
join(dirname(root), "local-agents", "memory-harvest", "instances", `memory-harvest-${slug}`),
|
|
207
253
|
join(root, "local-agents", "memory-harvest", "instances", `memory-harvest-${slug}`),
|
|
208
254
|
join(root, "tmp-agents", "memory-harvest", "instances", `memory-harvest-${slug}`),
|
|
209
255
|
];
|
|
210
256
|
if (harvesterHomes.some((h) => existsSync(h))) skip("harvester already running for this instance");
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
upsert(root, { name: "memory-harvest", instructions: readFileSync(join(dirname(fileURLToPath(import.meta.url)), "..", "agents", "memory-harvest.md"), "utf8") });
|
|
215
|
-
agentDef = core.findAgent(root, "memory-harvest");
|
|
216
|
-
}
|
|
217
|
-
// The harvester is service infrastructure: ALWAYS ephemeral, regardless of
|
|
218
|
-
// its on-disk kind (it homes as a local soul so it is uncommitted).
|
|
219
|
-
agentDef = { ...agentDef, kind: "capability" };
|
|
220
|
-
// Harvest model: explicit okf settings win (hook env, or resolved from config
|
|
221
|
-
// when agent-initiated); else the integration's default.
|
|
222
|
-
let harvestModel = settings["harvest-model"];
|
|
223
|
-
if (!harvestModel && context) {
|
|
224
|
-
try { harvestModel = core.resolveOatsConfig(context).layers?.knowledge?.settings?.["harvest-model"]; } catch { /* config unreadable: use default */ }
|
|
225
|
-
}
|
|
226
|
-
harvestModel = harvestModel || DEFAULT_HARVEST_MODEL;
|
|
257
|
+
// Effective command settings are injected by capability dispatch. No
|
|
258
|
+
// resolved-config read crosses the public package boundary.
|
|
259
|
+
const harvestModel = settings["harvest-model"] || DEFAULT_HARVEST_MODEL;
|
|
227
260
|
const workDir = realpathSync(join(home, "work"));
|
|
228
261
|
const realSoul = realpathSync(sDir);
|
|
229
262
|
const harvName = `memory-harvest-${slug}`;
|
|
@@ -235,11 +268,10 @@ _(the single next action — keep this current; a fresh session on any model res
|
|
|
235
268
|
// harvester judges notes exactly as usual, but the deliverable is DIRECT
|
|
236
269
|
// edits to the canonical soul — no commit, no PR: there is nothing to
|
|
237
270
|
// version. It must not touch the owner's work tree.
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
repo: context, work: "attached", workDir, model: harvestModel,
|
|
241
|
-
|
|
242
|
-
});
|
|
271
|
+
const task = `Harvest the pending notes of live LOCAL-SOUL instance "${inst}" (agent "${agName}") into its soul — by direct edits, no commit.\n\n- Source notes: ${notesDir} (${notes.join(", ")})\n- Soul knowledge bundle to update: ${join(realSoul, "knowledge")}\n- Soul skills dir (for procedure-shaped notes): ${join(realSoul, "skills")}\n- This soul is LOCAL (uncommitted, gitignored): edit those soul files IN PLACE. Do NOT run git commit — not for the soul, and not in ./work (the shared tree belongs to the working instance; leave it untouched).\n- Follow your memory-harvest skill for everything else: promote/merge/drop each note, knowledge vs skill routing, index + log discipline, validate the bundle, DELETE processed notes from the source notes/ dir.\n- Then run \`oats retire ${harvName} --self\`.`;
|
|
272
|
+
r = await spawnHarvester(harvestSpawnArgs({
|
|
273
|
+
slug, parent: inst, repo: context, work: "attached", workDir, model: harvestModel,
|
|
274
|
+
}), task);
|
|
243
275
|
} else if ((process.env.OATS_WORK || meta.work) === "workspace") {
|
|
244
276
|
// WORKSPACE-MODE instance: ./work is the whole workspace, not a git repo —
|
|
245
277
|
// the harvester may NOT commit there. The soul lives in its own home repo
|
|
@@ -248,11 +280,11 @@ _(the single next action — keep this current; a fresh session on any model res
|
|
|
248
280
|
const soulRepo = gitRootOf(realSoul);
|
|
249
281
|
if (!soulRepo) skip("workspace-mode soul is not inside a git repo — nowhere to deliver a PR");
|
|
250
282
|
const relSoul = realSoul.slice(soulRepo.length + 1);
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
repo: soulRepo, work: "worktree",
|
|
254
|
-
|
|
255
|
-
});
|
|
283
|
+
const task = `Harvest the pending notes of live WORKSPACE-MODE instance "${inst}" (agent "${agName}") into its soul — delivered as a PR.\n\n- Source notes: ${notesDir} (${notes.join(", ")})\n- Your ./work is a dedicated worktree of the soul's home repo (${soulRepo}), branch memory-harvest/${slug}.\n- Soul knowledge bundle to update: ./work/${join(relSoul, "knowledge")}\n- Soul skills dir (for procedure-shaped notes): ./work/${join(relSoul, "skills")}\n- Follow your memory-harvest skill: promote/merge/drop each note, knowledge vs skill routing, index + log discipline, validate the bundle, DELETE processed notes from the source notes/ dir, commit once (prefixed "memory-harvest:").\n- Then push the branch and open a PR (\`git push -u origin memory-harvest/${slug}\` then \`gh pr create --fill\`). Do NOT merge it; the humans/owners of ${soulRepo} review soul changes. If gh is unavailable, push the branch and report the compare URL.\n- Finally run \`oats retire ${harvName} --self\` (keep the branch: --self only).`;
|
|
284
|
+
r = await spawnHarvester(harvestSpawnArgs({
|
|
285
|
+
slug, parent: inst, repo: soulRepo, work: "worktree",
|
|
286
|
+
branch: `memory-harvest/${slug}`, model: harvestModel,
|
|
287
|
+
}), task);
|
|
256
288
|
} else {
|
|
257
289
|
// Repo-resident souls: write to the soul AS SEEN FROM THE WORK TREE, so the
|
|
258
290
|
// promotion commits onto the instance's own branch. Otherwise the canonical soul.
|
|
@@ -260,16 +292,15 @@ _(the single next action — keep this current; a fresh session on any model res
|
|
|
260
292
|
const soulTarget = realSoul.startsWith(realRepo + "/")
|
|
261
293
|
? join(workDir, realSoul.slice(realRepo.length + 1))
|
|
262
294
|
: realSoul;
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
repo: context, work: "attached", workDir, model: harvestModel,
|
|
266
|
-
|
|
267
|
-
});
|
|
295
|
+
const task = `Harvest the pending notes of live instance "${inst}" (agent "${agName}") into its soul.\n\n- Source notes: ${notesDir} (${notes.join(", ")})\n- Soul knowledge bundle to update: ${join(soulTarget, "knowledge")}\n- Soul skills dir (for procedure-shaped notes): ${join(soulTarget, "skills")}\n- You are ATTACHED to the instance's work tree (./work) — commit your promotions there as a single commit, prefixed "memory-harvest:".\n- Follow your memory-harvest skill: promote/merge/drop each note, knowledge vs skill routing, index + log discipline, validate the bundle, DELETE processed notes from the source notes/ dir (so they are not re-harvested), commit, then run \`oats retire ${harvName} --self\`.`;
|
|
296
|
+
r = await spawnHarvester(harvestSpawnArgs({
|
|
297
|
+
slug, parent: inst, repo: context, work: "attached", workDir, model: harvestModel,
|
|
298
|
+
}), task);
|
|
268
299
|
}
|
|
269
300
|
if (JSON_MODE) jsonOk({ harvest: "spawned", instance: r.instance, window: r.tmux?.window || null });
|
|
270
301
|
out({ meta: { harvestSpawn: r.instance, window: r.tmux?.window } });
|
|
271
302
|
} catch (e) {
|
|
272
|
-
if (JSON_MODE) jsonFail("E_HARVEST_FAILED", `harvest spawn failed (notes are safe on disk): ${e.message || e}`);
|
|
303
|
+
if (JSON_MODE) jsonFail(e.code || "E_HARVEST_FAILED", `harvest spawn failed (notes are safe on disk): ${e.message || e}`);
|
|
273
304
|
warn(`harvest spawn failed (notes are safe on disk): ${e.message || e}`);
|
|
274
305
|
}
|
|
275
306
|
} else if (event === "retire") {
|
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
"capability": "oats.okf",
|
|
3
3
|
"command": "okf",
|
|
4
4
|
"version": "1.4.1",
|
|
5
|
-
"compatibility": { "oats": ">=0.
|
|
5
|
+
"compatibility": { "oats": ">=0.19.0" },
|
|
6
6
|
"layer": "knowledge",
|
|
7
7
|
"description": "Knowledge layer via OKF: soul bundles, instance memory (STATE.md/log.md/notes/), continuous post-commit harvest into the soul (commit, PR, or direct-edit for local souls), craft + memory skills, validator.",
|
|
8
8
|
"requires": [],
|
|
9
|
+
"agents": [
|
|
10
|
+
"agents/memory-harvest"
|
|
11
|
+
],
|
|
9
12
|
"skills": [
|
|
10
13
|
"skills"
|
|
11
14
|
],
|