ws://localhost:8000/ws (override with NEXT_PUBLIC_PORTO_WS_URL).
Every client frame is JSON with a type. The server replies with JSON (type plus payload). Include trace_id if you want it echoed on errors.
v1 is single-user. The socket is not a public multi-tenant API. Treat it as a local control plane for your own stack.
Connect
resume_from with the last seq you saw. The server replays the missed tail or answers reset.
Minimum loop to talk
1
Open a project
After you create one in the UI (or already have an id):
2
Connect a provider (cloud / local)
{ "type": "credential_set", "provider": "ollama", "auth_type": "api_key" }.provider values: claude, openai, gemini, deepseek, xai, openrouter, ollama, local, mistral, mistral_vibe. CLI harnesses (claude_code, codex, grok) do not take a key here.For local, you may also send nickname and instance_id (omit instance_id to add a new row).3
List live models (optional)
{ "type": "provider_models", "provider": "ollama", "models": [ … ] }.4
Add an agent
{ "type": "agent_added", "agent_id": "…", "handle": "ollama", "room_id": "…" }.5
Chat
cancel_chat. Emergency halt: emergency_stop.Credential messages
Agent messages
Useful
add_agent fields (all optional except room_id + provider):
string
Display name. Empty → provider label.
string
Pin. Omit for the provider default.
string
Role string. Shapes default grants and charter.
string
Local / network host already saved in Settings.
string
Which Local / network row to use.
string[]
["file_ops","web"], ["*"], or [].Errors
Handler failures return:type is a structured error, not a socket close. The connection stays open.

