From 8a3d52965e4ccdea49f6f380df1fe90d84a111ac Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Mon, 3 Aug 2026 20:10:49 +0200 Subject: [PATCH] remove per-agent connection polling Agent connection state is now derived from session.connected in session/idx, updated via refreshSessions(). No longer spawns N processes for N agents. - Remove m_agentConnections hash and per-agent NinePConnection instances - Remove readAgentRecords() (dead code - agents come from session/idx) - reconcileAgentConnections() now just updates m_agentConnected from m_sessions This eliminates one ollie-9p process per agent, leaving only: - 1 daemon eventwait stream (for all events) - 1 chat stream (for active agent only) - 1 state stream (for active agent only) --- gui/ollie9pclient.cpp | 86 ++++++++++++------------------------------- gui/ollie9pclient.h | 11 +----- 2 files changed, 24 insertions(+), 73 deletions(-) diff --git a/gui/ollie9pclient.cpp b/gui/ollie9pclient.cpp index e6da079..2fee0be 100644 --- a/gui/ollie9pclient.cpp +++ b/gui/ollie9pclient.cpp @@ -142,24 +142,6 @@ QString Ollie9pClient::agentKey(const QString &sessionId, const QString &agentId return sessionId + "\n" + agentId; } -QList Ollie9pClient::readAgentRecords(const QString &sessionId) -{ - QList result; - if (sessionId.isEmpty()) return result; - // Use immutable ID — the 9P namespace resolves it via alias. - const QString raw = QString::fromUtf8(run9p({"ls", "session/" + sessionId + "/agent"})).trimmed(); - for (const QString &name : raw.split('\n', Qt::SkipEmptyParts)) { - if (name == "new") continue; - const QString path = "session/" + sessionId + "/agent/" + name; - const QString id = QString::fromUtf8(run9p({"read", path + "/id"})).trimmed(); - AgentRecord r; - r.id = id.isEmpty() ? name : id; - r.name = name; - result.append(r); - } - return result; -} - void Ollie9pClient::setDaemonConnected(bool connected) { if (m_daemonConnected == connected) return; @@ -229,71 +211,49 @@ void Ollie9pClient::setAgentConnected(const QString &key, bool connected) void Ollie9pClient::startAgentConnections() { - reconcileAgentConnections(); + // Agent connection state now comes from session/idx (refreshSessions) + // and eventwait events. No per-agent processes needed. + // Just start streams for the active agent if we have one. + if (!m_activeSessionId.isEmpty() && !m_agentId.isEmpty()) { + startActiveAgentStreams(); + } } -// reconcileAgentConnections ensures one NinePConnection per known agent. -// Connections use immutable session/agent IDs in their 9P paths. The server -// namespace lists directories by mutable display name, but accepts the -// immutable UUID as an alias during path walks. This means connection paths -// remain valid across session/agent renames without reconnection. +// reconcileAgentConnections updates agent connected state from m_sessions. +// Connection state is derived from session.connected in session/idx. void Ollie9pClient::reconcileAgentConnections() { if (!m_daemonConnected) return; - QSet wanted; + + // Update connected state for all agents based on session data for (const QVariant &value : std::as_const(m_sessions)) { const QVariantMap session = value.toMap(); const QString sid = session.value("id").toString(); + const bool isConnected = session.value("connected").toBool(); + for (const QVariant &agentValue : session.value("agents").toList()) { const QVariantMap agent = agentValue.toMap(); const QString aid = agent.value("id").toString(); const QString key = agentKey(sid, aid); - wanted.insert(key); - if (m_agentConnections.contains(key)) continue; - auto *connection = new NinePConnection(this); - m_agentConnections.insert(key, connection); - connect(connection, &NinePConnection::connected, this, [this, key]() { - const bool reconnected = m_seenAgentConnections.contains(key); - m_seenAgentConnections.insert(key); - setAgentConnected(key, true); - if (key == agentKey(m_activeSessionId, m_agentId)) { - if (reconnected) - emit sessionConnected(); + + bool wasConnected = m_agentConnected.value(key, false); + m_agentConnected[key] = isConnected; + + // Emit signal if this is the active agent and connection state changed + if (key == agentKey(m_activeSessionId, m_agentId)) { + if (!wasConnected && isConnected) { + emit sessionConnected(); startActiveAgentStreams(); } - }); - connect(connection, &NinePConnection::disconnected, this, [this, key]() { - // Agent may have been killed — refresh to get authoritative state - // rather than showing a transient red indicator. - refreshSessions(); - }); - // Retry with backoff (5s, max 3 retries) to avoid hammering the server - // when the connection file exists but returns an error (e.g., toolsConn nil). - connection->start(ollie9pBin(), {"-a", serverAddr(), "read", "--open-marker", - "session/" + sid + "/agent/" + aid + "/connection"}, 5000, true, 3); + } } } - for (auto it = m_agentConnections.begin(); it != m_agentConnections.end();) { - if (wanted.contains(it.key())) { - ++it; - continue; - } - it.value()->disconnect(this); // prevent stale disconnect signals - it.value()->stop(); - it.value()->deleteLater(); - m_agentConnected.remove(it.key()); - it = m_agentConnections.erase(it); - } } void Ollie9pClient::stopAgentConnections() { - for (NinePConnection *connection : std::as_const(m_agentConnections)) { - connection->disconnect(this); - connection->stop(); - } - qDeleteAll(m_agentConnections); - m_agentConnections.clear(); + // No per-agent connections to stop anymore + m_agentConnected.clear(); } // handleEvent processes a single event from eventwait. diff --git a/gui/ollie9pclient.h b/gui/ollie9pclient.h index dde6b3f..516f2ed 100644 --- a/gui/ollie9pclient.h +++ b/gui/ollie9pclient.h @@ -74,13 +74,6 @@ public: Q_INVOKABLE QString getConfig(); Q_INVOKABLE QStringList getAgents(const QString &sessionId); - struct AgentRecord { - QString id; - QString name; - bool connected = false; - }; - - QList readAgentRecords(const QString &sessionId); void setAgentConnected(const QString &key, bool connected); QString agentKey(const QString &sessionId, const QString &agentId) const; Q_INVOKABLE void setActiveAgentId(const QString &agentId); @@ -141,9 +134,7 @@ private: QVariantMap m_rootModels; QString m_currentBackend; bool m_daemonConnected = false; - QHash m_agentConnections; - QHash m_agentConnected; - QSet m_seenAgentConnections; + QHash m_agentConnected; // Derived from session.connected in session/idx NinePConnection *m_daemon = nullptr; // Agent state cache — updated via delta events