Root cause of a spawned agent's failed first send: fleet agents are born not knowing how to reach their peers. Inject a comms cheat-sheet + peer roster into each fleet agent's system prompt via composeContract (the runtime-agnostic path every `mosaic yolo <runtime>` agent hits), so it can reach the orchestrator and peers from its first turn. - src/fleet/comms-onboarding.ts (standalone, no fleet.ts coupling): parseRosterAgents (name/class/host/ssh/socket), renderPeerReach (data-driven agent-send command), buildFleetCommsBlock (own [host:session] identity + agent-send path + peer table + FLIP-to-reply + `agent send --verify`=ACCEPTED), readFleetCommsBlock (reads roster.yaml; '' when not a member). - launch.ts composeContract appends it only when MOSAIC_AGENT_NAME is set + the agent is in the roster (no-op for non-fleet launches). - roster.schema.json: optional per-agent host/ssh/socket (cross-host + socket addressing; manual cross-host listing is a pre-federation stopgap). Address rendering is fully data-driven per Mos's acceptance criteria: - cross-host: local → `-s <session>` (no -H); remote → `-H <ssh> -s <session>`. - socket: named → `-L <socket>`; unset → default socket (no -L) — matches the LIVE socket, never blindly inherits the roster's tmux.socket_name. Verified: 14 onboarding + 9 composeContract tests (parse, render local/remote/ fallback/equal-host/socket-none/named/combined, build, situational read + composeContract injection w/ correct cross-host+socket addrs, no-op when MOSAIC_AGENT_NAME unset). tsc/eslint/prettier/sanitize clean. Refs #620 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EsgTQzV5YUGk1JtCLP4B83
163 lines
4.6 KiB
JSON
163 lines
4.6 KiB
JSON
{
|
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
"$id": "https://mosaicstack.dev/schemas/fleet-roster.schema.json",
|
|
"title": "Mosaic Fleet Roster",
|
|
"type": "object",
|
|
"required": ["version", "transport", "agents"],
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"version": {
|
|
"const": 1
|
|
},
|
|
"transport": {
|
|
"const": "tmux"
|
|
},
|
|
"tmux": {
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"socket_name": {
|
|
"type": "string",
|
|
"default": "mosaic-factory"
|
|
},
|
|
"socketName": {
|
|
"type": "string",
|
|
"default": "mosaic-factory"
|
|
},
|
|
"holder_session": {
|
|
"type": "string",
|
|
"default": "_holder"
|
|
},
|
|
"holderSession": {
|
|
"type": "string",
|
|
"default": "_holder"
|
|
}
|
|
}
|
|
},
|
|
"defaults": {
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"working_directory": {
|
|
"type": "string",
|
|
"default": "~/src"
|
|
},
|
|
"workingDirectory": {
|
|
"type": "string",
|
|
"default": "~/src"
|
|
}
|
|
}
|
|
},
|
|
"runtimes": {
|
|
"type": "object",
|
|
"additionalProperties": {
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"reset_command": {
|
|
"type": "string"
|
|
},
|
|
"resetCommand": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"agents": {
|
|
"type": "array",
|
|
"minItems": 1,
|
|
"items": {
|
|
"type": "object",
|
|
"required": ["name", "runtime"],
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"name": {
|
|
"type": "string",
|
|
"pattern": "^[A-Za-z0-9_.-]+$"
|
|
},
|
|
"runtime": {
|
|
"type": "string"
|
|
},
|
|
"class": {
|
|
"type": "string"
|
|
},
|
|
"host": {
|
|
"description": "Host the agent runs on (hostname or IP). Absent = the fleet host. Used by onboarding-injection to render cross-host comms addresses. Manual cross-host listing is a pre-federation stopgap; federation (W1) auto-discovers later.",
|
|
"type": "string"
|
|
},
|
|
"ssh": {
|
|
"description": "SSH target (user@host) for a cross-host peer, so onboarding renders the `agent-send.sh -H <user@host>` form. Optional; only needed for agents on a different host than the fleet.",
|
|
"type": "string"
|
|
},
|
|
"socket": {
|
|
"description": "tmux socket the agent's session runs on. Onboarding renders `-L <socket>` when set; absent = the default socket (no `-L`). Must match the LIVE socket, not blindly inherit the roster's tmux.socket_name.",
|
|
"type": "string"
|
|
},
|
|
"working_directory": {
|
|
"type": "string"
|
|
},
|
|
"workingDirectory": {
|
|
"type": "string"
|
|
},
|
|
"workingDirectory": {
|
|
"type": "string"
|
|
},
|
|
"model_hint": {
|
|
"type": "string"
|
|
},
|
|
"modelHint": {
|
|
"type": "string"
|
|
},
|
|
"persistent_persona": {
|
|
"oneOf": [{ "type": "boolean" }, { "type": "string" }]
|
|
},
|
|
"persistentPersona": {
|
|
"oneOf": [{ "type": "boolean" }, { "type": "string" }]
|
|
},
|
|
"reset_between_tasks": {
|
|
"type": "boolean"
|
|
},
|
|
"resetBetweenTasks": {
|
|
"type": "boolean"
|
|
},
|
|
"kickstart_template": {
|
|
"type": "string"
|
|
},
|
|
"kickstartTemplate": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"connector": {
|
|
"description": "Orchestrator chat connector (F4). Optional — absent means tmux (back-compat). Secrets (access/bot tokens) come from the environment, never this file.",
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"required": ["kind"],
|
|
"properties": {
|
|
"kind": {
|
|
"enum": ["tmux", "discord", "matrix"]
|
|
},
|
|
"matrix": {
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"required": ["homeserver_url", "user_id", "room_id"],
|
|
"properties": {
|
|
"homeserver_url": { "type": "string" },
|
|
"user_id": { "type": "string" },
|
|
"room_id": { "type": "string" }
|
|
}
|
|
},
|
|
"discord": {
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"required": ["channel_id"],
|
|
"properties": {
|
|
"channel_id": { "type": "string" }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|