Skip to content

Migration Guide — v1.3.29

This release introduces two admin-chat-parity and dashboard-parity features that clean up long-standing inconsistencies between the Telegram transport, the admin panel, and the mobile clients. Most changes are additive, but the dashboard / system status refactor drops several response fields — any caller that still reads them needs an update.

Core version: 1.3.29 — see pyproject.toml.


At a glance

Area Change Breaking?
GET /api/system/status agents, sessions, cron_jobs, tasks_total, tasks_active, agent_count, active_tasks_count removed Yes
GET /api/dashboard/summary New — canonical dashboard payload with localized activity No (additive)
GET /api/activity/recent New — localized, typed activity feed No (additive, legacy /api/activity kept)
GET /api/activity/{id}?verbose=1 New — single-event lookup with raw source No (additive)
WebSocket streaming frames Now carry session_id + agent Soft-breaking for clients that ignored/asserted their absence
WebSocket chat_message event New envelope type No (additive)
Accept-Language on dashboard/activity Server-side localized strings No

1. /api/system/status — removed counters

What changed

Prior to 1.3.29 /api/system/status returned both machine metrics and a bag of counters for the dashboard:

// OLD response
{
  "cpu": {...},
  "ram": {...},
  "disk": {...},
  "uptime_seconds": 12345,
  "uptime_human": "3h 25m",
  "agents": 4,                // <- removed
  "agent_count": 4,            // <- removed (legacy alias)
  "sessions": 12,              // <- removed
  "cron_jobs": 7,              // <- removed
  "tasks_total": 150,          // <- removed
  "tasks_active": 2,           // <- removed
  "active_tasks_count": 2      // <- removed (legacy alias)
}

Counters depended on live state from several subsystems (agents.json, tasks.json, cron_jobs.json, session registry), which meant every request touched all of them and cache invalidation was impossible to reason about. Dashboard refresh also triggered a fan-out of 3–4 parallel REST calls.

What to do

Read counters from /api/dashboard/summary.counters instead:

// GET /api/dashboard/summary → counters block
{
  "counters": {
    "agents_total": 4,
    "agents_online": 2,
    "active_tasks": 2,
    "running_crons": 0,
    "failed_last_24h": 1
  }
}

Field mapping:

Old (/system/status) New (/dashboard/summary.counters)
agents / agent_count agents_total (plus new agents_online)
tasks_active / active_tasks_count active_tasks
cron_jobs (not exposed directly — use /api/cron or inspect running_crons for currently-firing jobs)
sessions (not exposed — use /api/sessions if you need the list)
tasks_total (not exposed — use /api/tasks?limit=…)

/api/system/status still returns CPU / RAM / disk / uptime, so any caller that only used those fields needs no changes.


2. /api/dashboard/summary — new endpoint

One-shot payload for the dashboard page; replaces the previous /agents + /system/status + /activity + /cron fan-out.

GET /api/dashboard/summary
Accept-Language: ru
Authorization: Bearer <jwt>

Response:

{
  "system": {
    "cpu": {...}, "ram": {...}, "disk": {...},
    "uptime_seconds": 12345,
    "uptime_human": "3 ч 25 мин"  // localized
  },
  "counters": {
    "agents_total": 4, "agents_online": 2,
    "active_tasks": 2, "running_crons": 0, "failed_last_24h": 1
  },
  "recent_activity": [
    {
      "id": "evt-...",
      "type": "task_completed",         // machine-typed for filtering
      "title": "Задача «Flight search» завершена",
      "subtitle": "2 мин назад • main",
      "agent_name": "main",
      "timestamp": "2026-04-19T01:05:00Z",
      "severity": "success"
    }
  ]
}

Notes:

  • Accept-Language: ru|en controls title/subtitle strings. Default is ru.
  • Server-side 5 second cache per (language, ACL bucket) absorbs refresh spikes.
  • recent_activity is always the 10 latest events visible to the caller; to page further, use /api/activity/recent.
  • ACL filtering: if the caller has allowed_agents set, both counters.agents_total and recent_activity only include events for those agents.

See admin-panel.md — System endpoints for the reference table.


3. /api/activity/recent — canonical activity stream

GET /api/activity/recent?limit=20
Accept-Language: ru

Every event carries:

Field Type Purpose
id string Stable event id — usable with /api/activity/{id}
type enum Machine type (for filtering): task_completed, task_failed, agent_started, agent_stopped, cron_fired, cron_failed, webhook_called, auth_login
title localized string Human-readable summary. Never raw AGENT_TASK_FINISHED_OK-style codes
subtitle localized string Relative time + agent
agent_name string|null
timestamp ISO-8601 UTC
severity enum info / success / warning / error

Unknown machine types receive a localized fallback title — raw machine strings are never leaked to the UI. /api/activity/{id}?verbose=1 adds the raw source record + a source field (notifications, tasks, cron, audit) for admin debugging.

Legacy /api/activity

The old /api/activity endpoint is kept as-is for backward compatibility — it still returns raw type / message pairs. New clients should use /api/activity/recent; the old one will not get new features.


4. WebSocket streaming — session_id + agent on every frame

What changed

Pre-1.3.29, streaming frames on /ws/admin looked like:

{ "type": "text_delta", "text": "partial..." }

Now every streaming frame carries the originating chat session and agent:

{ "type": "text_delta", "text": "partial...", "session_id": "sess-abc", "agent": "main" }

This applies to:

  • text_delta
  • tool_activity
  • system_status
  • result
  • error

Why

Admin WS connections are now registered per-user (keyed on the JWT sub claim), so a streaming turn is fanned out to every live socket of that user (other tabs, mobile PWA, etc.). Without session_id / agent, sibling tabs cannot route events to the correct chat when the user has different sessions open on different devices.

What to do

Clients that ignore unknown fields need no changes. Clients that asserted field shapes (strict schema validators, TypeScript type-guards) must widen their types. Reference types live in:

Routing rule on the client:

if (frame.session_id && frame.session_id !== activeSessionId(frame.agent)) {
  // Update history for frame.session_id in-place, but don't swap the visible chat.
  updateSessionInBackground(frame.agent, frame.session_id, frame);
  return;
}
render(frame);

5. New WebSocket event: chat_message

Telegram-transport deliveries are now mirrored into the admin chat.

{
  "type": "chat_message",
  "kind": "task_result",
  "role": "agent",
  "agent": "main",
  "session_id": "sess-abc",
  "content": "...",
  "meta": { "task_id": "t-1", "task_name": "Flight search" },
  "timestamp": 1700000000
}

kind is one of:

  • task_result — a background task finished
  • task_question — a task asked a question via ask_parent.py
  • interagent — response from ask_agent / ask_agent_async
  • text — a plain background message

These are persisted server-side via sygen_bot/api/chat_history.py and attached to the agent's most recently updated REST chat session (fallback when the envelope has no named session id), so a client reload still sees them.

Action required

  • Clients that render WS events must handle the new chat_message type explicitly. If ignored, the live view will miss messages the REST history has — a reload will suddenly surface them.
  • No REST changes are needed: existing GET /api/chat/sessions/{id}/messages already returns them.

6. Mirrors / cross-device sync

The admin WS registry (_user_ws) now maintains a per-user map of live sockets. When any socket of a user receives a streaming event, it is also pushed to every sibling socket (excluding the originator), respecting per-socket agent ACLs.

Impact on clients:

  • A user with dashboard open on desktop + PWA will see the same live stream on both.
  • An inactive tab will receive events it did not initiate — this is intentional and keeps chat state consistent across devices.
  • Broadcasts are ACL-aware: a sibling socket that is not allowed to see a given agent receives nothing.

Client checklist

  • [ ] Any caller reading agent_count / active_tasks_count / agents / tasks_active on /api/system/status → migrate to /api/dashboard/summary.counters
  • [ ] Dashboard fan-outs to /agents + /system/status + /activity → collapse into a single /api/dashboard/summary call
  • [ ] WebSocket event handler accepts optional session_id + agent on text_delta / tool_activity / system_status / result / error
  • [ ] WebSocket event handler routes chat_message envelopes into the session store
  • [ ] Activity page points at /api/activity/recent and renders title + subtitle; raw type is only used for filtering
  • [ ] Accept-Language is set on dashboard / activity requests if the UI is non-Russian

References