서론
대부분의 백엔드 서비스는 상태(state)를 소유합니다. 이들은 데이터베이스에 기록하고, 세션을 추적하며, 사용자가 흐름 속 어디에 있는지 기억합니다.
하지만 StareBrain의 백엔드는 그러한 작업을 전혀 수행하지 않습니다. /next-step으로 들어오는 모든 요청은 자체적으로 완결됩니다. 백엔드는 클라이언트가 보내는 것을 읽고, 다음 행동을 결정하며, 이를 반환할 뿐입니다. 아무것도 기록되지 않습니다. 아무것도 저장되지 않습니다. 응답이 나가는 순간 세션은 종료됩니다.
이는 의도적인 아키텍처 선택이며, 에이전트가 플랜(plan), 단계 히스토리(step history), 그리고 복구(recovery)를 처리하는 방식에 실제적인 영향을 미칩니다.
요청과 응답
단일 에이전트 단계의 전체 스키마는 다음과 같습니다:
`class AgentStepRequest(BaseModel):
task: str
screen_description: str
...
요청에 포함된 내용을 주목해 보세요: steps_so_far, plan, current_subgoal, known_facts. 클라이언트는 호출할 때마다 세션의 전체 히스토리를 전송합니다. 백엔드는 어떤 것도 조회하지 않습니다. 단지 완전한 컨텍스트를 수신하여 그것만으로 결정합니다.
무상태(Stateless)인 이유
이러한 결정을 이끈 세 가지 이유가 있습니다:
1. 클라이언트가 이미 상태를 가지고 있다.
Android 앱은 행동을 실행하고, 화면을 읽고, 무슨 일이 일어났는지 추적합니다. 정의상 가장 정확한 세션 상태 그림을 가지고 있는 주체는 바로 클라이언트입니다. 이를 백엔드 데이터베이스에 중복 저장하는 것은 일관성 문제(consistency problem)를 추가합니다. 즉, 둘 중 어느 것이 맞는지 분기될 때 문제가 발생할 수 있습니다.
2. 재개 가능성이 무료다.
만약 백엔드가 세션 도중에 충돌하더라도, 클라이언트는 동일한 요청을 다시 보내서 재개할 수 있습니다. 복구해야 할 세션도 없고, 복원해야 할 체크포인트(checkpoint)도 없습니다. 다음 호출은 마지막으로 확인된 단계가 멈춘 바로 그 지점부터 이어집니다.
3. 확장이 사소하다 (Scaling is trivial).
무상태 백엔드는 어떠한 친화성(affinity)도 갖지 않습니다. 즉, 어떤 인스턴스든 모든 요청을 처리할 수 있습니다. 세션 고정(sticky sessions), 공유 캐시, 동기화해야 할 분산 상태가 없습니다.
클라이언트가 소유하는 것
백엔드가 무상태이기 때문에, 다음 항목에 대한 진실의 원천(source of truth)은 클라이언트입니다:
`data class AgentSession(
val task: String,
val stepsSoFar: MutableList<String>,
...
각 응답 후 클라이언트는 로컬 세션을 업데이트하고 다음 호출에서 업데이트된 상태를 전송합니다. 백엔드는 이전 단계를 기억할 필요가 전혀 없습니다. 그 정보는 요청에 포함되어 있기 때문입니다.
계획 수립 및 재계획 (Plan and Replan)
응답은 plan(초기 계획, 첫 단계에서 설정됨) 또는 replan(세션 중간의 대체 계획)을 담을 수 있습니다. 클라이언트는 도착하는 어떤 것이든 적용합니다:
`fun applyResponse(session: AgentSession, response: AgentStepResponse) {
response.plan?.let { session.plan.addAll(it) }
response.replan?.let {
...
}
백엔드가 제안하고, 클라이언트가 적용하며 소유권을 가집니다. 다음 요청에서 업데이트된 계획은 AgentStepRequest.plan을 통해 다시 전송됩니다.
트레이드오프 (The Tradeoff)
무상태(Stateless)라는 것이 공짜는 아닙니다. 요청 페이로드(request payload)는 세션 길이에 따라 증가합니다. steps_so_far와 known_facts가 누적되기 때문입니다. 긴 세션일수록 호출당 더 많은 데이터를 전송하게 됩니다.
이를 완화하는 방법은 다음과 같습니다: 임계값(threshold)을 넘어서면 steps_so_far는 단순히 추가되는 것이 아니라 요약됩니다. 또한, known_facts는 키-값 쌍(key-value pairs)으로 구조화되어 백엔드가 증가하는 서사를 파싱할 필요 없이 관련 사실들을 읽을 수 있게 합니다.
또 다른 트레이드오프가 있습니다: 클라이언트가 상태를 잃으면 (앱 종료, 작업 도중 기기 재부팅 등), 세션 자체가 사라집니다. 서버 측 복구 메커니즘이 없습니다. 현재 사용 사례인 모바일 장치에서의 짧은 에이전트(agentic) 작업에는 이것이 허용 가능합니다. 하지만 몇 시간에 걸쳐 진행되는 장시간 작업의 경우, 이 부분에 대한 재고가 필요할 것입니다.
시리즈 요약:
Part 1: Two-axis state model + DENIED_UNRESOLVED
Part 2: Context propagation + confidence thresholds
Part 3: Late confirmation race + terminal state check
Part 4: Provider capability tiers
Part 5: Stale plan protection with FreshCtx
Part 6: Stateless backend architecture ← 오늘 다룬 내용