Conversation
Add a Model row to the board's ticket detail panel, beside the Agent row: pin which model a ticket's session runs, defaulting to the login's default. The agent must advertise its probed models for the picker to offer real choices. Hub-owned durable state like the agent pin (XERK-38), not an agent-ledger fan-out: the model rides the spawnTicket command the hub already routes, so the hub is the one party that remembers the choice. - server.js: ticket-models store (/data/ticket-models.json), POST /api/jira/<siteKey>/<issueKey>/model (200-authoritative), ticketModels on the /api/agents payload + SSE event, orgModelAliases allowlist (union of the org's hosts' probed models.available + static family aliases), and the pinned model carried on spawnTicket from both the Start button and the auto-start sweep. - hub-agent.py: spawn_ticket takes the model and passes it to spawn, which validates it via resolve_model like any composer spawn (bad model -> error card; unpinned -> default, unchanged). - board.js/board.html: the Model row, picker (Default + the org's curated aliases), modelPinOf/modelChoices/prettyModel, saveModel POST, the ticketModels SSE listener, and refreshOpenTicket pin-change detection. - Android parity: ticketModels/ModelsInfo/TicketModelPin wire types, FleetRepository plumbing, Board.kt merge + helpers, the Model row + picker in BoardScreen, BoardViewModel.setTicketModel, the HubApi endpoint. Verified: 610 turma JS + 731 agent Python tests pass; new cases across server.test.js, board.test.js, TestSpawnTicket, and BoardTest.kt. Drove the board in a real browser (headless Chrome over CDP): the Model row renders the default, the picker offers Default + the probed aliases, selecting pins the model (POST 200, hub payload updated, row reads "Opus — set by you"), and releasing drops the pin.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds a Model row to the board's ticket detail panel, right below the Agent row (XERK-38): the operator can pin which model a ticket's session runs, defaulting to the login's default model. The agent must advertise its probed models for the picker to list actually-available choices — exactly what the ticket asks for.
Mirrors the agent-pin pattern end to end (hub + agent + web + Android).
How it works
spawnTicketcommand the hub already routes (ticketModelPin→ the command'smodel), so the hub is the one party that has to remember the choice.POST /api/jira/<siteKey>/<issueKey>/model({model}to pin,{auto:true}/{model:"default"}to release) → authoritative 200, stored in/data/ticket-models.json, riding/api/agentsas top-levelticketModels+ aticketModelsSSE event.orgModelAliases: union of the org's hosts' probedmodels.available, non-bracketed, + the static family aliases). The agent still re-validates host-side (resolve_model) before launch.spawn_tickettakes the model and passes it tospawn(validated like any composer spawn). Unpinned → nomodelon the command → default, unchanged.board.js/board.html): the Model row + picker (Default + the org's curated aliases viamodelChoices/prettyModel),saveModelPOST, the SSE listener, and pin-change repaint.ticketModels/ModelsInfo/TicketModelPinwire types,FleetRepositoryplumbing,Board.ktmerge + helpers, the Model row + picker inBoardScreen,BoardViewModel.setTicketModel, theHubApiendpoint, andBoardTest.ktcases.The pin also feeds the auto-start sweep (an auto-started To Do ticket carries its pinned model).
Verification
server.test.js(/modelendpoint, spawnTicket-carries-model, persistence),board.test.js(modelPinOf/modelPickerHtml/modelChoices/prettyModel/mergeSites),TestSpawnTicket(model lands on session + command line, dispatch), andBoardTest.kt(parity).Default (Sonnet 5) — the agent's default model), the picker offersDefault + opus/sonnet/haiku(the org's probed aliases), selecting Opus POSTs/model→ 200, the hub payload gains the pin, the row reads "Opus — set by you", and releasing to Default drops the pin.android-ci.yml.Base cut from
origin/main.