Goal
Add a reusable contributor role — the persona the agentwire helper session carries by default for non-owner users. It knows the agentwire-dev repo and helps a newcomer:
- get set up: install, configure, orchestrate their own project sessions, and have the system explained to them;
- submit ideas easily via a clean, correctly-labeled GitHub issue;
- if they want, fork the repo and build their own version as a first-class supported path.
This generalizes "what the owner uses the agentwire session for" so it works for people who are not the repo owner.
Decision: one universal role, owner overrides locally
There is no separate owner role file. contributor is the published default for everyone. The owner layers a small local override role on their own machine that says "you own this repo — no fork needed, you can push to main / write labels / merge directly." Because both the owner's role file and .agentwire.yml stay untracked, the override never ships to anyone else.
- Everyone →
contributor (bundled, default).
- Owner →
contributor + a local owner-override role (untracked, see below).
How the role system actually works (verified — read before implementing)
The original draft mis-described two mechanisms. Ground truth:
-
There is no "universal default role for all sessions." The only role auto-injected into every session is soul (inject_soul, agentwire/roles/__init__.py:168). Persona roles are NOT auto-injected. The worktree-session analogy in the original draft is wrong: worktree-session is intrinsic etiquette tied to a derived session kind (INTRINSIC_ETIQUETTE at agentwire/roles/__init__.py:209, applied by resolve_roles at :243). contributor is a plain persona role and has no such hook — a session carries it only because a command hardcodes it or .agentwire.yml lists it.
-
Roles are markdown files, discovered by name (discover_role, agentwire/roles/__init__.py:296), first match wins:
- project:
<repo>/.agentwire/roles/<name>.md (note: .agentwire/ is gitignored)
- user:
~/.agentwire/roles/<name>.md
- bundled:
agentwire/roles/<name>.md (ships in the package)
-
.agentwire.yml only lists role names in its roles: field — it does not contain role bodies. agentwire new reads it (agentwire/session_cli.py:135-141 → resolve_roles). .agentwire.yml is personal/untracked in this repo: .gitignore lists both .agentwire.yml (line 47) and .agentwire/ (line 53), and ensure_gitignored (agentwire/project_config.py:274) re-appends .agentwire.yml to .gitignore whenever config is saved through the CLI — unless the file is already tracked, in which case it's deliberately left alone (:299-305, "already tracked = deliberate team choice; don't fight it"). So git doesn't literally forbid a commit (git add -f could force one), but in normal use the owner's .agentwire.yml stays untracked and never ships — which is exactly what the override design relies on.
-
agentwire dev already exists (cmd_dev, agentwire/system_cli.py:42, registered at :569-573). It creates the agentwire tmux session in the source dir and hardcodes role_names = inject_soul(["agentwire"], …) at :57, at {agent}-bypass (:64-65). It ignores .agentwire.yml entirely — cmd_dev never calls load_project_config, so it reads neither project roles: nor type:. So today the helper session carries the agentwire role, not contributor.
Scope
In scope:
- Author the bundled
contributor role (agentwire/roles/contributor.md).
- Make the helper session (
agentwire dev) carry it by default.
- Ship a committed config example so a fresh clone of agentwire-dev spawns a contributor session via
agentwire new, while the owner's untracked .agentwire.yml still wins.
- Document the owner-override role.
Out of scope:
Approach
1. The contributor role — agentwire/roles/contributor.md, same frontmatter+markdown format as agentwire/roles/agentwire.md. Encode:
- Onboarding: help install/configure agentwire, wire up the user's own projects, explain sessions / panes / orchestration.
- Repo awareness:
CLAUDE.md, docs/wiki/INDEX.md, issue conventions (feature:* / area:* / priority:* taxonomy; issue body = full plan; Closes #N in the PR body).
- Easy idea submission: coach a clean, correctly-labeled issue draft; search for duplicates first.
- Fork-based PR flow (load-bearing — the contributor's
gh is their OWN account, with no upstream push/label/merge rights):
gh repo fork dotdevdotdev/agentwire-dev (or reuse an existing fork)
- branch + push on the fork; open a PR into
dotdevdotdev/agentwire-dev:main
- never assume upstream push, label-write, or merge rights.
- Build-your-own: present forking-to-maintain-a-variant as a first-class supported path.
- Tone: friendly maintainer-proxy; never impersonate the owner.
The role becomes discoverable automatically once the file exists (it's picked up by discover_role / agentwire roles list).
2. Make the helper session carry it. Edit cmd_dev (agentwire/system_cli.py:57) to inject contributor instead of (or in addition to) agentwire for the default helper session. NOTE: because cmd_dev hardcodes its role list and ignores .agentwire.yml, the committed example in step 3 will not affect agentwire dev — this code edit is required for the #619 helper session to carry contributor. Decide whether the dev session should be ["contributor"], ["agentwire", "contributor"], or keep agentwire for owner-on-own-repo and switch the default. (Coordinate with #619, which owns the agentwire dev UX.)
3. Committed config example for agentwire new so a fresh clone spawns a contributor session out of the box, with the owner's local file winning:
- Commit the template under a distinct filename (e.g.
.agentwire.yml.example), not .agentwire.yml. Two reasons: the CLI keeps .agentwire.yml gitignored (ensure_gitignored), and — more fundamentally — a committed default and the owner's local override would otherwise be the same path, so they can't coexist with the local file winning. A separate template filename lets the committed default and a local untracked .agentwire.yml both exist. The template's roles: lists contributor.
- New resolution logic required —
find_project_config / load_project_config (agentwire/project_config.py:195 and :224) today read only .agentwire.yml and have no awareness of any .example. Add a fallback so that, when no live .agentwire.yml exists, the committed example is used; when a local untracked .agentwire.yml exists, it wins. (copy_files already carries .agentwire.yml into worktrees — agentwire/config.py:91 — so worktree dispatch is unaffected.)
4. Owner-override role — document that the owner drops an override role markdown file at ~/.agentwire/roles/<name>.md (or <repo>/.agentwire/roles/<name>.md, which is gitignored) and references it in their untracked .agentwire.yml roles: (e.g. [contributor, owner-override]). merge_roles (agentwire/roles/__init__.py:120) concatenates instructions in list order, so the override (listed last) rides on top of contributor with recency weight. No code change needed for this — it's documentation of an existing capability. Caveat: this .agentwire.yml roles: path only takes effect for agentwire new (which reads the yml); the agentwire dev helper ignores .agentwire.yml (see point 4 above), so if the owner wants the override on the dev session specifically, that comes from the step-2 cmd_dev decision, not from the yml.
Verification
- Non-owner
gh account: agentwire dev (or agentwire new in a fresh clone) → session offers fork/branch/PR flow, never pushes upstream; from "I have an idea about X" it produces a correctly-labeled issue draft.
- Owner install with the override role: same session, but it knows not to fork and can act directly (push to main, write labels, merge).
- Role discovery:
agentwire roles show contributor resolves the bundled file.
- Resolution precedence: with a local
.agentwire.yml present, the committed example is ignored; with it absent, the example's contributor role is used.
- Regression:
uv run pytest (role-resolution and project-config tests live in tests/unit/test_roles.py and tests/unit/test_project_config.py).
Goal
Add a reusable
contributorrole — the persona the agentwire helper session carries by default for non-owner users. It knows the agentwire-dev repo and helps a newcomer:This generalizes "what the owner uses the agentwire session for" so it works for people who are not the repo owner.
Decision: one universal role, owner overrides locally
There is no separate owner role file.
contributoris the published default for everyone. The owner layers a small local override role on their own machine that says "you own this repo — no fork needed, you can push to main / write labels / merge directly." Because both the owner's role file and.agentwire.ymlstay untracked, the override never ships to anyone else.contributor(bundled, default).contributor+ a local owner-override role (untracked, see below).How the role system actually works (verified — read before implementing)
The original draft mis-described two mechanisms. Ground truth:
There is no "universal default role for all sessions." The only role auto-injected into every session is
soul(inject_soul,agentwire/roles/__init__.py:168). Persona roles are NOT auto-injected. Theworktree-sessionanalogy in the original draft is wrong:worktree-sessionis intrinsic etiquette tied to a derived session kind (INTRINSIC_ETIQUETTEatagentwire/roles/__init__.py:209, applied byresolve_rolesat:243).contributoris a plain persona role and has no such hook — a session carries it only because a command hardcodes it or.agentwire.ymllists it.Roles are markdown files, discovered by name (
discover_role,agentwire/roles/__init__.py:296), first match wins:<repo>/.agentwire/roles/<name>.md(note:.agentwire/is gitignored)~/.agentwire/roles/<name>.mdagentwire/roles/<name>.md(ships in the package).agentwire.ymlonly lists role names in itsroles:field — it does not contain role bodies.agentwire newreads it (agentwire/session_cli.py:135-141→resolve_roles)..agentwire.ymlis personal/untracked in this repo:.gitignorelists both.agentwire.yml(line 47) and.agentwire/(line 53), andensure_gitignored(agentwire/project_config.py:274) re-appends.agentwire.ymlto.gitignorewhenever config is saved through the CLI — unless the file is already tracked, in which case it's deliberately left alone (:299-305, "already tracked = deliberate team choice; don't fight it"). So git doesn't literally forbid a commit (git add -fcould force one), but in normal use the owner's.agentwire.ymlstays untracked and never ships — which is exactly what the override design relies on.agentwire devalready exists (cmd_dev,agentwire/system_cli.py:42, registered at:569-573). It creates theagentwiretmux session in the source dir and hardcodesrole_names = inject_soul(["agentwire"], …)at:57, at{agent}-bypass(:64-65). It ignores.agentwire.ymlentirely —cmd_devnever callsload_project_config, so it reads neither projectroles:nortype:. So today the helper session carries theagentwirerole, notcontributor.Scope
In scope:
contributorrole (agentwire/roles/contributor.md).agentwire dev) carry it by default.agentwire new, while the owner's untracked.agentwire.ymlstill wins.Out of scope:
agentwire devdiscoverability / first-run onboarding nudge — tracked in Onboarding: point users at the existingagentwire devhelper session + make it carry the contributor role by default #619.soul/ intrinsic-etiquette injection machinery.Approach
1. The
contributorrole —agentwire/roles/contributor.md, same frontmatter+markdown format asagentwire/roles/agentwire.md. Encode:CLAUDE.md,docs/wiki/INDEX.md, issue conventions (feature:*/area:*/priority:*taxonomy; issue body = full plan;Closes #Nin the PR body).ghis their OWN account, with no upstream push/label/merge rights):gh repo fork dotdevdotdev/agentwire-dev(or reuse an existing fork)dotdevdotdev/agentwire-dev:mainThe role becomes discoverable automatically once the file exists (it's picked up by
discover_role/agentwire roles list).2. Make the helper session carry it. Edit
cmd_dev(agentwire/system_cli.py:57) to injectcontributorinstead of (or in addition to)agentwirefor the default helper session. NOTE: becausecmd_devhardcodes its role list and ignores.agentwire.yml, the committed example in step 3 will not affectagentwire dev— this code edit is required for the #619 helper session to carrycontributor. Decide whether the dev session should be["contributor"],["agentwire", "contributor"], or keepagentwirefor owner-on-own-repo and switch the default. (Coordinate with #619, which owns theagentwire devUX.)3. Committed config example for
agentwire newso a fresh clone spawns a contributor session out of the box, with the owner's local file winning:.agentwire.yml.example), not.agentwire.yml. Two reasons: the CLI keeps.agentwire.ymlgitignored (ensure_gitignored), and — more fundamentally — a committed default and the owner's local override would otherwise be the same path, so they can't coexist with the local file winning. A separate template filename lets the committed default and a local untracked.agentwire.ymlboth exist. The template'sroles:listscontributor.find_project_config/load_project_config(agentwire/project_config.py:195and:224) today read only.agentwire.ymland have no awareness of any.example. Add a fallback so that, when no live.agentwire.ymlexists, the committed example is used; when a local untracked.agentwire.ymlexists, it wins. (copy_filesalready carries.agentwire.ymlinto worktrees —agentwire/config.py:91— so worktree dispatch is unaffected.)4. Owner-override role — document that the owner drops an override role markdown file at
~/.agentwire/roles/<name>.md(or<repo>/.agentwire/roles/<name>.md, which is gitignored) and references it in their untracked.agentwire.ymlroles:(e.g.[contributor, owner-override]).merge_roles(agentwire/roles/__init__.py:120) concatenates instructions in list order, so the override (listed last) rides on top ofcontributorwith recency weight. No code change needed for this — it's documentation of an existing capability. Caveat: this.agentwire.yml roles:path only takes effect foragentwire new(which reads the yml); theagentwire devhelper ignores.agentwire.yml(see point 4 above), so if the owner wants the override on thedevsession specifically, that comes from the step-2cmd_devdecision, not from the yml.Verification
ghaccount:agentwire dev(oragentwire newin a fresh clone) → session offers fork/branch/PR flow, never pushes upstream; from "I have an idea about X" it produces a correctly-labeled issue draft.agentwire roles show contributorresolves the bundled file..agentwire.ymlpresent, the committed example is ignored; with it absent, the example'scontributorrole is used.uv run pytest(role-resolution and project-config tests live intests/unit/test_roles.pyandtests/unit/test_project_config.py).