ADR-014 (Docker-first deployment): resolves OQ-21, OQ-22, OQ-23, OQ-34, OQ-35, OQ-36, OQ-37. Docker is the primary deployment model. Redis/Postgres in same network. Config via mounted volumes. Single- container restart for v1. Migrations block startup. ADR-015 (Dev spoke, not opencode): resolves OQ-16, OQ-17, OQ-26, OQ-28, OQ-51, OQ-55. Replaces opencode integration with a compiled dev spoke binary. Hub owns session format. Opencode compat is an import tool, not an architectural constraint. Adds OQ-61 (dev spoke operations) and OQ-62 (dev spoke distribution). ADR-016 (Hub-own schema): resolves OQ-18, OQ-19. Hub defines its own canonical message/part format. JSONB is implicitly versioned. Flat parts for v1. Compaction is a hub concern (pruning), not opencode's. ADR-017 (Hub-first roles): resolves OQ-26, OQ-28, OQ-51 (overlapping with ADR-015). Hub is database-first for roles. Seeded by migrations. No file sync needed. hub.createRole for custom roles. Also narrowed: OQ-04 (service accounts), OQ-05 (git SSO out of scope), OQ-08 (spoke-side concern), OQ-09 (v1: reconnect only), OQ-11 (dev spoke replaces container spoke), OQ-29 (hub-only concern), OQ-41 (gitea ops are optional spoke concern). Deferred: OQ-52 (memory), OQ-55 (anthropic import). Net result: 15 resolved, 7 narrowed, 2 deferred out of 62 total. 39 remain open, down from 60 in the original tracker.
4.2 KiB
ADR-017: Hub-first role definitions (database, not files)
- Status: Accepted
- Date: 2026-05-26
- Deciders: alkdev
Context
The original architecture (from agent-roles.md) defined a three-phase role system:
- Phase 1 (current): Roles defined in
.opencode/agents/*.mdmarkdown files - Phase 2: A
roles.syncoperation that ingests.opencode/agents/*.mdfiles into arolestable - Phase 3: Database-authoritative roles, with markdown files only for version control editing
This phased approach was designed around opencode's convention of file-based agent definitions. With ADR-015 (dev spoke instead of opencode integration), opencode is no longer a core dependency. The hub manages its own roles.
Decision
The hub is database-first for roles from day one. There is no Phase 1 or Phase 2 transition from files to database. Roles are defined in the roles table in Postgres, seeded by migrations for the built-in roles (architect, decomposer, coordinator, implementation-specialist, code-reviewer, architecture-reviewer, research-specialist, poc-specialist).
Built-in roles
The hub's migration files seed the standard SDD roles:
| Role | Mode | Key Permission Pattern |
|---|---|---|
| architect | primary | read, write, webSearch — no bash |
| architecture-reviewer | subagent | read, grep — read-only |
| code-reviewer | subagent | read, grep, bash (read-only) |
| coordinator | primary | read, worktree_*, bash (limited) — no implementation |
| decomposer | primary | read, taskgraph — no bash |
| implementation-specialist | primary | read, write, edit, bash, webSearch — scoped to worktree |
| poc-specialist | primary | read, write, edit, bash, webSearch — scoped to research worktree |
| research-specialist | subagent | webSearch, read, write — no bash |
Role API
Roles are managed via hub operations:
hub.listRoles— list available roleshub.getRole— get role definition by namehub.createRole— create a custom role (requires admin scope)hub.updateRole— update role definition (requires admin scope)
Custom roles can be created at runtime via hub.createRole. No file sync is needed.
Opencode agent mapping
When importing opencode sessions (if an opencode-spoke is built later), the mapping from opencode's agent field to the hub's roleName is:
Opencode agent |
Hub roleName |
|---|---|
"build" |
"implementation-specialist" |
"plan" |
"decomposer" |
"general" |
"coordinator" |
"explore" |
"research-specialist" |
This mapping is a compat concern in the import tool, not a core architecture concern.
Consequences
Positive: No file-based role sync system to build. No .opencode/agents/ directory dependency. Roles are queryable, type-safe, and managed through the hub's operation interface. Custom roles can be created programmatically. The hub doesn't need a roles.sync operation.
Negative: Role definitions can't be easily version-controlled in markdown files alongside the code. Role creation requires the hub API (or seeding via migrations). If role editing in a text editor is desired later, a roles.export/roles.import operation can be built, but this is not a v1 concern.
Open questions resolved by this decision
| OQ | Resolution |
|---|---|
| OQ-26 | No roles.sync from .opencode/agents/*.md needed. Hub is database-first. Role definitions are seeded by migrations. |
| OQ-28 | No Agent.generate() equivalent needed for v1. Custom roles are created via hub.createRole. |
| OQ-51 | No file→DB migration needed. Hub started in the database-first state. |
Open questions narrowed by this decision
| OQ | Narrowing |
|---|---|
| OQ-04 | Service account provisioning is now a generalized question: hub.createAccount operation for programmatic creation. For v1, manual creation with keypal CLI is sufficient. LLM-specific email conventions (like glm-5.1@alk.dev) are deployment-specific, not core architecture. |
| OQ-05 | SSO with Gitea is out of scope for a generalized hub. Git provider integration (Gitea, GitHub, etc.) is a spoke concern via operations, not through SSO. For v1, Gitea is accessed via the dev spoke's git operations, not via shared auth. |