subagent-driven-development
OBobra/superpowers298k近 7 天新增 2,631 个 star
Orchestrator skill for resolving multi-daemon binding contention. Use when you (the orchestrator) detect an `agent.binding.contested` sync event indicating two daemons are racing for the same agent — explicitly rebind ownership to a chosen target daemon so subsequent dispatches route deterministically. Implements Gap G-2-⑤ on top of the Wave 2-B2 `/api/im/agent-bindings/:agentImUserId/rebind` endpoint.
When two daemons (typically Mac Studio at home + a k8s pod) both run agent.host.declare
for the same agent, the first one wins ownership and the second registers a
contested binding. The user UI surfaces a "binding contested" badge and the cloud's
resolveAgentDaemonRoute keeps routing dispatches to the original owner — but a
human-driven or orchestrator-driven decision is needed to converge.
This skill is the orchestrator's tool for that decision. It calls the server's authoritative rebind endpoint, which atomically:
im_agent_bindings.boundDaemonId to the chosen target daemonboundBy='user-explicit' (or 'orchestrator')agent.binding.rebound) so other daemons drop their hosting
state for the agentagent.binding.contested arrives in your inbox.Do not use it for:
metadata.daemonId overrides in single dispatches instead).There is no generic cloud im get / cloud im post subcommand in the
runtime CLI contract described here (sdk/cloud/ and sdk/prismer/).
A dedicated cloud agent rebind verb is not landed either. The skill calls
the cloud HTTP endpoint directly via curl (or the equivalent fetch from the
orchestrator's MCP runtime), authenticating with the daemon API key already
present in the environment as $PRISMER_API_KEY:
Before a write, resolve the actual workspace, agent ID, target daemon ID and
authorized identity from the current runtime. aip-identity is not a bundled
prerequisite; use the available identity/read-only binding interface. Missing
identity or unavailable endpoint is a stop condition, not permission to invent
IDs or switch accounts. Confirm migration impact and drain in-flight work.
# Inspect the current bindings first.
curl -fsS \
-H "Authorization: Bearer $PRISMER_API_KEY" \
"$PRISMER_CLOUD_BASE/api/im/workspaces/<workspaceId>/agent-bindings"
# Issue the rebind. Requires workspace owner / active orchestrator / admin.
curl -fsS \
-X POST \
-H "Authorization: Bearer $PRISMER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"targetDaemonId":"<daemonId>","targetDaemonKind":"local","reason":"<short reason>"}' \
"$PRISMER_CLOUD_BASE/api/im/agent-bindings/<agentImUserId>/rebind"
PRISMER_CLOUD_BASE defaults to the cloud the daemon paired with
(https://prod.docbrew.cn in prod, http://127.0.0.1:3000 in local dev).
For pure-API use (orchestrator runtime, MCP tool, etc.), the underlying call is:
POST /api/im/agent-bindings/:agentImUserId/rebind
Authorization: Bearer <daemon api key>
Content-Type: application/json
{
"targetDaemonId": "daemon-mac-studio-01", // required, must exist in workspace
"targetDaemonKind": "local", // optional: 'k8s' | 'local' | 'edge'
"targetDaemonLabel": "Mac Studio (home)", // optional display label
"reason": "user prefers desktop daemon while on home network"
}
Successful response:
{
"ok": true,
"data": {
"binding": {
"agentImUserId": "u_agent_ceo",
"boundDaemonId": "daemon-mac-studio-01",
"boundDaemonKind": "local",
"boundDaemonLabel": "Mac Studio (home)",
"boundBy": "user-explicit",
"boundAt": "2026-05-22T16:30:00.000Z"
},
"inFlightTaskCount": 3,
"previousDaemonId": "daemon-k8s-pod-7f4"
}
}
Failure modes worth handling explicitly:
| Status | Code (in body) | Meaning |
|---|---|---|
400 | target daemon ... not registered | Target daemonId not in im_containers for this workspace. |
400 / missing field | targetDaemonId is required | Body parse / missing required field — surface as developer error. |
403 | Forbidden | Caller is not workspace owner / orchestrator / admin. |
404 | Agent not found ... | Agent has no im_agent_cards row (deleted? wrong id?). |
404 | No binding row exists ... | Agent never ran host.declare. Tell the daemon to declare first. |
409 | target daemon ... is stopped | Target container has stoppedAt set — pick a different target. |
agent.binding.contested sync events. The orchestrator's sync inbox
delivers these out of band; the skill consumes them.lastDispatchAt skew (fresher),contestCount (less flapping).inFlightTaskCount > 0 on the previous owner,
surface this to the user ("3 tasks finishing on the old daemon before switching").
The orchestrator should usually let those finish — the rebind takes effect for
subsequent dispatches, in-flight tasks complete via the existing route.targetDaemonId + reason. Optional kind/label improve UI
readability./workspaces/:wsId/agent-bindings again and confirm boundDaemonId
matches your target. If still divergent, the daemon may have re-declared between
read and write — repeat once.im_containers — calling with a fictional id returns 400. Don't guess.'unspecified' which is opaque to the user.After successfully calling rebind:
[claim-agent-ownership] rebound agent=<agentImUserId> from=<previousDaemonId>
to=<targetDaemonId> in-flight-on-previous=<n>
reason="<reason>"
Then surface to the user as a chat message: who you rebound, where to, why, and how many tasks are still finishing on the old daemon (if any).
After a 4xx/5xx failure: report the HTTP status + the server's error code + message verbatim. Do not silently retry on 400/403/404 — those need human attention.
POST /api/im/agent-bindings/:agentImUserId/rebind
(Wave 2-B2, src/im/api/agent-bindings.ts)IMAgentBinding (migration 410, fields:
boundDaemonId, boundDaemonKind, boundDaemonLabel, boundBy, contestCount,
contestedSince)agent.binding.contested — emitted by AgentBindingService
when a second daemon declares an agent already bound to a different daemon.agent.binding.rebound — broadcast after
successful rebind so all daemons + UI consumers drop stale routing state.IMTaskLog-style entry with
boundBy='user-explicit' / 'orchestrator' and the supplied reason; visible in the
Devices panel.Inbox event: { type: 'agent.binding.contested', agentImUserId: 'u_agent_ceo',
existingDaemonId: 'daemon-k8s-pod-7f4',
contendingDaemonId: 'daemon-mac-studio-01' }
Orchestrator inspects bindings:
curl -H "Authorization: Bearer $PRISMER_API_KEY" \
"$PRISMER_CLOUD_BASE/api/im/workspaces/ws_abc/agent-bindings"
Decision: user is on home network, prefer Mac Studio (local).
Action:
curl -X POST -H "Authorization: Bearer $PRISMER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"targetDaemonId":"daemon-mac-studio-01","targetDaemonKind":"local",
"reason":"user on home network, preferring local daemon"}' \
"$PRISMER_CLOUD_BASE/api/im/agent-bindings/u_agent_ceo/rebind"
Response: boundDaemonId='daemon-mac-studio-01', inFlightTaskCount=2
Orchestrator messages user: "Pinned Team Manager agent to your Mac Studio. 2 tasks finishing on
the cloud pod first."
User: "Make sure DesignAgent always runs on my laptop, not the k8s pod."
Orchestrator (after reading bindings):
curl -X POST -H "Authorization: Bearer $PRISMER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"targetDaemonId":"daemon-laptop-9a","targetDaemonKind":"local",
"reason":"user-explicit: pin to laptop"}' \
"$PRISMER_CLOUD_BASE/api/im/agent-bindings/u_design_agent/rebind"
Orchestrator tries to rebind to a daemon that just went offline:
POST .../rebind { targetDaemonId: "daemon-k8s-pod-7f4", ... }
← 409 { error: "target daemon daemon-k8s-pod-7f4 is stopped" }
Orchestrator should:
1. Re-read /workspaces/<wsId>/agent-bindings to find a live alternative.
2. If only one daemon remains and it's already the current owner, do nothing.
3. Otherwise retry with the live candidate.
targetDaemonKind / targetDaemonLabel that contradict the server's
inference (e.g. claiming kind=k8s for a deviceType=local container). The server
trusts the body so misuse leads to a wrong-shaped UI badge.inFlightTaskCount > 0 as a hard error — it's informational. The cloud
has already redirected new dispatches; existing in-flight work simply finishes on
the old route.orchestratorAgentId)
to make sure you're the active arbiter before mutating bindings.OBobra/superpowers298k近 7 天新增 2,631 个 star
MAmattpocock/skills288k近 7 天新增 10,178 个 star
MAmattpocock/skills288k近 7 天新增 10,178 个 star
MAmattpocock/skills288k近 7 天新增 10,178 个 star
AFaffaan-m/ECC278k近 7 天新增 4,046 个 star
AFaffaan-m/ECC278k近 7 天新增 4,046 个 star