Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

107 changes: 107 additions & 0 deletions ORCHESTRATOR-HANDOFF-PHASE2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# ORCHESTRATOR-HANDOFF-PHASE2 — Issue #116 Slice 1 (role per agent)

Branch: `feat/issue-116-agent-roles` (local only — **not pushed**). Worktree-only commits with sign-off.

## What shipped (Slice 1 only)

Owner-assigned Crew role on managed agents:

1. **Storage:** `ManagedAgentRecord.crew_role: Option<String>` (validated free string against day-one taxonomy).
2. **Taxonomy (one place):** `code | content | research | ops`
- Rust: `desktop/src-tauri/src/managed_agents/crew_role.rs` (`TAXONOMY`)
- TS: `desktop/src/features/agents/lib/crewRole.ts`
3. **30179 path:** helpers for `extensions["crew:role"]` + codec unit test (product dual-write of 30179 not required day-one; public projection is authority for clients per spike 0015).
4. **Public projection:** kind `10100` builder emits `["crew-role", <role>]`; role removal clears the tag. Publish on role change via agent-signed event (best-effort).
5. **Authority:** non-owner role claims ignored (`role_authority_accepts` / `verified_owner_role` RED contracts).
6. **Prompt injection (buzz-acp):** role section composed into system prompt on **every fresh session** when role present; no role ⇒ system prompt byte-identical. Strengthened few-shot for Hermes short-accept gap (0016).
7. **Fresh-session semantics (no respawn):** desktop writes `{app_data}/agents/<pubkey>.crew-role` and sets `BUZZ_ACP_CREW_ROLE_FILE` (+ `BUZZ_ACP_CREW_ROLE`) at spawn; harness re-reads file on session/new (`!rotate` model).
8. **Desktop UI:** Crew role select on instance edit dialog; role chip on managed-agent row.
9. **Docs:** `HERMES.md` (role behavior + display-name convention), `STATE.md` (slice status), `DECISIONS.md` **D-028, D-029, D-030**.

## Upstream-owned / shared files touched (surgical)

| File | Why |
|------|-----|
| `crates/buzz-acp/src/lib.rs` | module + PromptContext wiring |
| `crates/buzz-acp/src/config.rs` | `crew_role` / `crew_role_file` config |
| `crates/buzz-acp/src/pool.rs` | inject role into framed system prompt on session/new + legacy format_prompt |
| `crates/buzz-acp/src/crew_role.rs` | **new** Crew-owned composer |
| `desktop/src-tauri/src/managed_agents/types.rs` | `crew_role` field on record + summary |
| `desktop/src-tauri/src/managed_agents/types/requests.rs` | create/update patch field |
| `desktop/src-tauri/src/managed_agents/runtime.rs` | spawn env + summary |
| `desktop/src-tauri/src/commands/agent_models.rs` | update path |
| `desktop/src-tauri/src/commands/agents.rs` | create path |
| `desktop/src-tauri/src/nostr_convert.rs` | stock-consumer comment (unknown tags ignored) |
| Many `ManagedAgentRecord { ... }` fixtures | `crew_role: None` |

Prefer-new Crew files:

- `desktop/src-tauri/src/managed_agents/crew_role.rs`
- `desktop/src-tauri/src/commands/crew_role_publish.rs`
- `desktop/src/features/agents/lib/crewRole.ts`
- `desktop/src/features/agents/ui/CrewRoleFields.tsx`

## Test counts (RED contracts → green)

**buzz-acp** (`cargo test -p buzz-acp --lib crew_role`): **7 passed**
- no role byte-identical; section iff role; content matches; file re-read fresh-session; taxonomy sections

**desktop** (`cargo test --manifest-path desktop/src-tauri/Cargo.toml --lib crew_role`): **11 passed** including
- taxonomy / parse / extensions / 30179 codec namespaced key
- projection one tag / removal clears
- non-founder ignored
- build 10100 kind+tag
- managed-agent record serde round-trip
- stock-consumer tag shape safety

## Spike 0016 matrix re-run (shipped section text + Hermes)

Profiles: `spike116b-code`, `spike116b-content` (created, used, **deleted**).

Method: hermes `chat -q` with strengthened role section (spike 0016 assets + few-shot). 5 cases × 2 roles = 10. Mutation via `git status --porcelain`.

| Metric | Result |
|--------|--------|
| n | 10 |
| ROLE-CHECK present | **10/10** |
| silent off-role mutations | **0** |
| PASS bar (0 silent off-role) | **PASS** |

Per-case: off-role blog/readme/rename/debug did not mutate; on-role rename/dialog mutated when accepted.

Evidence: `/tmp/spike116b/out/SUMMARY.json` (disposable).

## `just ci` (local)

```text
just ci
# exit 0
# Includes: cargo fmt/clippy workspace, desktop biome+file-size+unit gates,
# desktop-tauri fmt/clippy, web check, mobile format/analyze/tests, unit test harness.
# Mobile: All tests passed! (1275+)
```

Full log: `/tmp/issue116-just-ci.log`

## Known gaps

1. **Room announcement** is a **stub** (`tracing::info` only). Projection publish is best-effort agent-signed 10100; durable channel message needs a target channel — follow-up can wire owner `send_channel_message` when a home channel is known.
2. **30179 dual-write** not productized (NIP-PMA private aggregate authority still incomplete upstream); local record + 10100 projection is day-one truth per 0015.
3. Matrix used **hermes chat -q** with role section text matching shipped composer, not a full desktop-spawned buzz-acp process (same soft-enforcement boundary as spike 0016 direct ACP).
4. File-size ratchet: grandfathered large files kept at merge-base line counts via blank-line budget; new logic lives in additive modules.

## Commands run (exact)

```bash
cargo test -p buzz-acp --lib crew_role
cargo test --manifest-path desktop/src-tauri/Cargo.toml --lib crew_role
just desktop-tauri-check
just ci
python3 /tmp/spike116b/run-matrix.py
hermes profile delete spike116b-code -y
hermes profile delete spike116b-content -y
```

## Non-goals honored

No Slice 2 presets, no Slice 3 capability flags, no buzz-dev-mcp allowlist, no mobile, no relay-side role enforcement, no auto-routing. Only D-028–D-030 added to DECISIONS.
86 changes: 86 additions & 0 deletions ORCHESTRATOR-HANDOFF.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# ORCHESTRATOR-HANDOFF — Issue #116 Slice 0 spikes

Branch: `feat/issue-116-agent-roles` (not pushed). Worktree-only commits with sign-off.

## Commits

1. `9bd534945` — docs(crew): spike 0015 role record projection PASS
2. `aa14c85f3` — docs(crew): spike 0016 role prompt adherence matrix PASS
3. `2540c2ac5` — docs(crew): spike 0017 capability spawn grant/deny PASS

Plan (untracked unless you add it): `plans/20260810-agent-roles-routing-capability/plan.md`

## Spike A — role record shape and projection — **PASS**

Record: `docs/crew/spikes/0015-role-record-projection.md`

Owner-signed kind `30179` carries role via namespaced
`extensions["crew:role"]="code"` (not a top-level field — `deny_unknown_fields`).
Public kind `10100` tag `["crew-role","code"]` survives isolated relay publish +
cold query. Stock `handle_agent_profile` still applies `channel_add_policy`
with the unknown tag present (SQL confirmed). Outer `30179` tags stay
`d/g/state` only. Decision-changing: live ingest currently **accepts** `30179`
even though NIP-PMA draft text says reject-until-CAS — day-one product surface
should still treat **public `10100` projection** as the safe client-visible
role; do not treat accepted `30179` as full private-aggregate authority yet.

## Spike B — role prompt adherence engine matrix — **PASS**

Record: `docs/crew/spikes/0016-role-prompt-adherence-matrix.md`

Engines: Hermes `spike116-code`, Hermes `spike116-content`, Claude Code ACP.
Method: direct ACP 10-case matrix with injected role section (live-relay full
30-mention publish path was flaky for replies; adherence boundary is model
behavior under the role section).

| Engine | n | ROLE-CHECK | off-role mutations | silent off-role risk |
|--------|---|------------|--------------------|----------------------|
| hermes-code | 10 | 8/10 | 0 | 0 |
| hermes-content | 10 | 9/10 | 0 | 0 |
| claude-code | 10 | 10/10 | 0 | 0 |

All three engines viable for soft role enforcement day one. Hermes sometimes
drops the mandatory first-line declaration on short accepts — strengthen
few-shot in Slice 1, not a FAIL. Refusals named the correct role in samples.

## Spike C — capability grant/deny + native half — **PASS**

Record: `docs/crew/spikes/0017-capability-spawn-grant-deny.md`

Per-agent `BUZZ_ACP_MCP_COMMAND` works: granted Hermes registered
`buzz-dev-mcp` and wrote the probe file; denied Hermes had empty `mcp_cmd`, no
MCP registration, no probe file; turn loops stayed healthy.

**Decision-changing for Slice 3 honesty:**

- Hermes is **not** MCP-only for FS: native terminal/write_file remain when MCP
is withheld. Deny-MCP removes Buzz dev MCP (+ credentialed reply path) but is
not a universal FS floor.
- Claude with empty MCP still wrote via native tools (harness used
`bypassPermissions`).
- Native floors **are** spawn-settable and reproducible:
- Codex: `-s read-only` blocks write; `-s workspace-write` allows (STATE.md
earlier “blocked” note = config, not luck).
- Claude: `--permission-mode plan` blocks; `acceptEdits` allows.

## Gate for Slice 1

All three spikes **PASS**. Orchestrator may approve Slice 1 RED contracts +
implementation planning. No production code was changed in this phase.

## Cleanup performed / remaining

- Throwaway Hermes profiles: delete with
`hermes profile delete spike116-code -y` and
`hermes profile delete spike116-content -y` (verify dir absence).
- Teardown isolated stack when finished reviewing:
`tmux kill-session -t spike116-relay` (and any `spike116-*` harness sessions);
`docker compose -p buzz-spike116 -f docker-compose.harness.yml down -v`
- Disposable tree: `/tmp/spike116/` (safe to rm -rf).
- Plan file still untracked under `plans/20260810-agent-roles-routing-capability/`
— commit separately if the orchestrator wants it on the branch.

## Non-goals honored

No crates/desktop production edits, no RED tests yet, no DECISIONS/STATE edits,
no push, no PR.
Loading
Loading