Conversation
…ed work Give each host a default runtime so auto-started/unpinned tickets and bare '+ New session' spawns run on the right runtime without pinning every one. - resolve_agent_type gains apply_default; the ONE fresh-spawn call passes it, resolving a blank agentType to default_runtime() (explicit pick/pin still wins, every rebuild/resume path keeps the stored runtime). - default_runtime() reads TURMA_DEFAULT_RUNTIME, self-validates against this host's own capability (agent_type_configured) and falls back to claude + logs when set-but-unrunnable — never a broken launch. UNSET -> claude, unchanged. - Heartbeat carries the EFFECTIVE default as top-level defaultRuntime; hub whitelists + coerces it (normalizeDefaultRuntime, kill-switch-consistent), absent -> claude. - Composer pre-selects the reported host default in the Runtime dropdown so a bare spawn shows which runtime it will use. - No hub dispatch capability-filter for the default path: the claiming host applies its own runnable default (findTicketHost unchanged). - Docs: turma-agent.env + agent-native.md (incl. the mixed-org nondeterminism limitation); android/PARITY.md line for the composer pre-select. - Tests: default_runtime/precedence (test_hub_agent.py), normalizeDefaultRuntime + heartbeat passthrough (server.test.js), composer pre-select (sessions.test.js).
…ost default
QA (FAIL) found the wire cannot distinguish 'explicit claude' from 'unpinned':
a composer 'Claude Code'/'Claude Code Local' pick omitted agentType, which now
resolves to the host default agent-side — so on a non-claude-default host an
explicit claude pick would run the default runtime, violating the precedence
contract (an explicit pick must win).
- sessions.html: a claude/local composer pick now SENDS agentType:'claude' when
the host default is non-claude (byte-for-byte unchanged on every claude-default
host). Test added.
- Documented the remaining KNOWN GAP: a per-ticket CLAUDE pin still does not
override a non-claude host default (the board Runtime row treats {runtime:claude}
as release), because closing it needs the picker to distinguish 'Auto — host
default' from a pinned 'Claude Code' across board.js/board.cjs/Board.kt/glasses
— a UX change deferred to a follow-up. Latent today (dsh/qwen kill switches off).
Noted in agent-native.md and turma-board.md.
…for Android qa-delta LOW: the composer behavior change (an explicit claude/local pick must SEND agentType:claude on a non-claude-default host) was not reflected in the android/PARITY.md P3 entry, so an Android implementer building only the pre-select would reintroduce the defect. Added the load-bearing half to the P3 line.
# Conflicts: # .claude/rules/agent-native.md # .claude/rules/turma-board.md
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.
XERK-521 — Per-host default runtime (
TURMA_DEFAULT_RUNTIME) for unpinned workGive each host a DEFAULT runtime so auto-started/unpinned tickets and bare
+ New sessionspawns run on the right runtime without pinning every one.Runtime-generic (claude/dsh/qwen). Per-host (env), resolved agent-side — the
alternative org/hub-level default was declined in the ticket.
Mechanism (single choke point)
TURMA_DEFAULT_RUNTIMEinagent/native/turma-agent.env(enum{claude,dsh,qwen}, charset/enum-gated). UNSET → claude, so every current host is byte-for-byte unchanged.resolve_agent_type), so no route diverges:explicit agentType → TURMA_DEFAULT_RUNTIME → claude. The default rung applies only viaapply_default=True, passed by the single fresh-spawn call; every rebuild/resume/migration path passes the STORED runtime withapply_default=False, so a resumed/migrated session keeps the runtime it already had (a blank pre-field record stays claude, never adopts the default).default_runtime()): checked against THIS host's own capability (dsh_configured/qwen_configured) — a host that setsqwenbut hasn't configured Qwen falls back to claude and SAYS so (log + the heartbeat's effective value). Never a broken launch.defaultRuntime; hub whitelists + coerces it (normalizeDefaultRuntimeinnormalizeRecord/HEARTBEAT_KNOWN_KEYS): absent → claude; a runtime whose fleet-wide kill switch is off is forced to claude for consistency with the zeroed capability block.findTicketHostroutes to the most-available host and the CLAIMING host applies its own runnable default (unchanged). Explicit pins still filter + block.agentType:"claude"on a non-claude-default host so the explicit pick beats the default (byte-for-byte unchanged on every claude-default host).Files
agent/hub-agent.py(precedence + env read + capability self-check +defaultRuntimeheartbeat),agent/native/turma-agent.env+.claude/rules/agent-native.md(env docs),turma/server.js(normalizeDefaultRuntime+ known key),turma/public/sessions.html(pre-select + explicit-claude),android/PARITY.md+.claude/rules/turma-board.md(parity/gap notes), tests.Acceptance
TURMA_DEFAULT_RUNTIME=qwenauto-starts an unpinned ticket → runs Qwen (agent resolves the effective default).agentType:"claude").TURMA_DEFAULT_RUNTIME=qwenwith Qwen not configured → falls back to claude, reports it, no broken launch.defaultRuntimereflects the effective (post-fallback) value; unset → claude; older hub/clients degrade.+ New sessionwith the dropdown untouched uses the host default (and the dropdown pre-selects it).{runtime:"claude"}as RELEASE (drops the pin), so a claude-pinned ticket carries noagentTypeand adopts the host default; dsh/qwen pins DO override (they store + forward their agentType). Closing it needs the Runtime picker to distinguish "Auto — host default" from a pinned "Claude Code" acrossboard.js+ vendoredboard.cjs+Board.kt+ glasses (a UX/design change) — see the gap notes inagent-native.md/turma-board.md. Latent today: dsh/qwen ship behind their kill switches (DSH_ENABLED/QWEN_ENABLED= false), so no host has a non-claude effective default until GA.Known limitation (by design)
Per-host is NONDETERMINISTIC in a MIXED org — an unpinned ticket runs whatever host frees a slot first defaults to. Determinism needs a per-ticket pin (XERK-515) or homogeneous hosts. Documented in the rules and accepted per the ticket.
Verification — QA (adversarial
qa+qa-delta)qa(pass 1): FAIL → found that an explicit "Claude Code" composer pick could not beat a non-claude host default (the wire couldn't distinguish "explicit claude" from "unpinned"). Fixed: the composer now sends explicitagentType:"claude"on a non-claude-default host. The second half QA reported (a claude ticket-PIN not overriding) is the documented KNOWN GAP above, deferred as a board picker redesign; QA agreed it does not block (latent behind the kill switches).qa-delta(pass 2): PASS → drove the real composer with an independent driver: on adefaultRuntime:"qwen"host an explicit Claude pick now posts{"repo":…,"agentType":"claude"}and the agent resolves it to claude over the default; claude-default/defaultless hosts remain byte-for-byte unchanged; mutation-checked the new guard test. No regressions.agent.tests.test_hub_agent1852 OK (5 skipped),turma/tests/server.test.js616/0,turma/tests/sessions.test.js113/0.SpawnDialog(nodefaultRuntimesupport yet — PARITY.md P3); real-browser DOM/click (no chromium in env — exercised via the exported composer functions).