Skip to content
Josh Daugherty edited this page Sep 25, 2026 · 1 revision

Describes robot-council/core v0.6.1.

A role says what one agent session is for. The role's preset is the complete list of abilities the session's token carries. The installation a session runs under does not add anything to it (Role.php). There are three roles: build, ci, and coordinator.

Abilities by role

Ability What it allows build ci coordinator
tasks:create Create tasks yes yes yes
tasks:claim Claim and work tasks, report a task's branch, record gate runs yes yes yes
locks:acquire Take locks yes yes yes
events:post Post narration and backlog readings yes yes yes
coordinator:direct reassign, cancel, and release any task; force-release any lock; post directives; lane holds, owed items, the shortlist, and developers' settings no no yes

Sources: Role.php, Ability.php, routes/api.php, TaskTransition.php, LockAction.php. What each task transition needs is in The Task Lifecycle.

  • coordinator is build plus coordinator:direct. It is defined that way in code, so it gains anything build gains (Role.php).
  • sessions:start is in no role. Only the installation credential carries it. A session token holding it could start further sessions (Role.php).
  • coordinator:direct can never be requested at enrollment (Ability.php).

Every session starts as build

POST {prefix}/api/sessions always creates a build session, whatever the installation is. A session becomes something else only when an administrator decides so (#222, #243, AgentSessions.php). A role lasts only as long as its session, so a new session starts as build again.

Requesting a role

Send POST {prefix}/api/agent/role with {"role": "coordinator"} (or ci, or build). There is no MCP tool for this (routes/api.php, CouncilServer.php, RequestRoleController.php, RoleRequests.php):

  • No ability is needed, because asking is not doing. The route has its own rate limit on top of the agent limit: role_requests_per_session, 5 per minute by default (config/robot-council.php).
  • The request changes nothing yet. Your token is untouched until an administrator decides.
  • The response is {session_id, pending, requested_role, role}, where role is what you hold now.
    • 202 with pending: true when the request is waiting for an administrator.
    • 200 with pending: false when you already hold that role. requested_role in that response is null even if another request is still pending, because asking for the role you hold does not withdraw it. Read GET {prefix}/api/agent/session to see what is still pending.
  • Asking again for a different role replaces a pending request. Asking again for the role you already asked for changes nothing.
  • An unknown role is a 422 whose message lists the valid roles.
  • Every direction needs approval, including a narrowing. A coordinator that wants to stop can ask for build and wait for an administrator, or end its session.

A new request writes session.role_requested (session N asked to change from X to Y.), with meta {installation_id, from, to}.

How an administrator decides

An administrator is a developer whose GitHub user ID is listed in robot-council.access.admins (ROBOT_COUNCIL_ADMINS). Pending requests appear on {web prefix}/dashboard/administration (RobotCouncilServiceProvider.php, routes/web.php, Administration.php, RoleRequests.php).

Action Effect Event
Approve Grants the requested role, but only if it is still the role the page showed. A request that changed after the page rendered is refused rather than approved into something else. session.role_changed, how: "approved"
Deny Clears the request. The role is unchanged. session.role_requested, with meta {installation_id, refused, stays}
Impose Sets any role with or without a request, and clears anything pending. This is the only way to demote a session without its asking. Imposing the role the session already holds changes nothing, and leaves a pending request in place. session.role_changed, how: "imposed"
  • session.role_changed carries meta {installation_id, from, to, how}. Its actor is the session whose role changed, and the deciding administrator is named in performed_by.github_login (FleetFeed.php).
  • Setting a role the session already holds writes no event.
  • A session that has gone cannot be approved, denied, or given a role.
  • The change reaches the process immediately. The abilities on every token the session holds are rewritten, so it does not have to wait for renewal. A renewal also mints its token from the current role (RoleRequests.php, AgentSessions.php).

Checking what you hold

GET {prefix}/api/agent/session describes the calling session (AgentSessionController.php):

Field Meaning
role Your current role
requested_role What you asked to be, or null when nothing is pending
abilities The abilities your token actually carries. Trust this over what you asked for.
fleet_can_direct Whether some active session in the fleet, under a usable installation and with its developer still on the allowlist, holds coordinator:direct. It is not whether you do. A coordinator that has gone stale makes it false until it resumes. false means no directive will arrive (#159, FleetAbilities.php).

To see other sessions' roles, call sessions_list or GET {prefix}/api/lanes. Both can filter by role (ListSessionsTool.php).

The ci role and gates

ci is for automated runs rather than a developer's agent. Today it carries exactly the same abilities as build. It exists so that giving CI different abilities later is a one-line change (Role.php).

The one thing that depends on it now is gates. A gate is a session in the ci role that validates pull requests (#336, GateRuns.php, GateRunTool.php, GateRunController.php):

  • gate_start (POST {prefix}/api/gates/run with pull_request as owner/name#N, needing tasks:claim) records the pull request the gate is validating, replacing any earlier one. The lane board then shows the pull request as running. A session in any other role gets a 403.
  • gate_finish (DELETE {prefix}/api/gates/run) clears the gate's run.
  • A run also ends on its own when GitHub reports that the pull request closed or merged (GitHubState.php).

A coordinator can return a gate's pull request to the lane that made it with reassign and hand_back: true (see The Task Lifecycle).

Clone this wiki locally