Touch agent presence on authenticated requests

This commit is contained in:
Abhimanyu Saharan
2026-02-07 04:34:16 +05:30
parent 999ec6d1bb
commit 844b521d00

View File

@@ -2,6 +2,7 @@ from __future__ import annotations
import logging
from dataclasses import dataclass
from datetime import timedelta
from typing import Literal
from fastapi import Depends, Header, HTTPException, Request, status
@@ -9,11 +10,15 @@ from sqlmodel import col, select
from sqlmodel.ext.asyncio.session import AsyncSession
from app.core.agent_tokens import verify_agent_token
from app.core.time import utcnow
from app.db.session import get_session
from app.models.agents import Agent
logger = logging.getLogger(__name__)
_LAST_SEEN_TOUCH_INTERVAL = timedelta(seconds=30)
_SAFE_METHODS = frozenset({"GET", "HEAD", "OPTIONS"})
@dataclass
class AgentAuthContext:
@@ -49,6 +54,34 @@ def _resolve_agent_token(
return None
async def _touch_agent_presence(
request: Request,
session: AsyncSession,
agent: Agent,
) -> None:
"""Best-effort update of last_seen/status for any authenticated agent request.
Heartbeats are the primary presence mechanism, but agents may still make API
calls (task comments, memory updates, etc). Touch presence so the UI reflects
real activity even if the heartbeat loop isn't running.
"""
now = utcnow()
if agent.last_seen_at is not None and now - agent.last_seen_at < _LAST_SEEN_TOUCH_INTERVAL:
return
agent.last_seen_at = now
agent.updated_at = now
if agent.status not in {"updating", "deleting"}:
agent.status = "online"
session.add(agent)
# For safe HTTP methods, endpoints typically do not commit. Persist the touch
# so agents that only poll/read still show as online.
if request.method.upper() in _SAFE_METHODS:
await session.commit()
async def get_agent_auth_context(
request: Request,
agent_token: str | None = Header(default=None, alias="X-Agent-Token"),
@@ -72,6 +105,7 @@ async def get_agent_auth_context(
resolved[:6],
)
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
await _touch_agent_presence(request, session, agent)
return AgentAuthContext(actor_type="agent", agent=agent)
@@ -103,4 +137,5 @@ async def get_agent_auth_context_optional(
resolved[:6],
)
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
await _touch_agent_presence(request, session, agent)
return AgentAuthContext(actor_type="agent", agent=agent)