Pillar 5 · Agent core (the loop)

Updated · View the entry on sijie.xyz ↗

The interpretation engine — where the product thesis (same corpus, per-audience presentation) executes. inference (backend/internal/conversation/inference/) wraps the CloudWego eino ADK loop (Anthropic → native Messages API; Gemini → its own adapter; every other provider → OpenAI-compatible adapter — eino_model.go); the frontend is a thin SSE consumer. Two recent structural moves define this pillar: the launchable core — backend/agentcore/ (deliberately outside internal/) exposes Driver + BuildVisitorAgent(driver, input), making the core independently launchable (bridge.go adapts a Driver onto the loop's internal ports; eval-harness/, a separate Go module, drives the same loop with its own EvalDriver — "eval is just another consumer"); and the entry-agnostic principle — the agent is entry-agnostic: entries fill a neutral _meta.standmeet/session and invoke.

Status (2026-09-07, 36789537d): Bridge/Driver landed, and the earlier fixture deviation is REMEDIATED — no fixtures remain in the core ("the Driver IS the environment"); inject-and-launch still test-only: BuildVisitorAgent has exactly one non-test caller (eval-harness/main.go:164, candidate.go:138) and nothing under backend/internal or backend/cmd imports agentcore. The prod web path builds its agent directly — internal/routes/public/sessions.go → capreg.AssembleVisitorBundle → inference.RunAgentTurn (the runtime promotion is the remaining step).

flowchart LR
  subgraph SM["StandMeet — prod box"]
    WEB["web visitor entry
(fills neutral _meta session)"] --> ASM["routes/public sessions.go
(capreg AssembleVisitorBundle)"]
    IM["im-bridge (Telegram)
via @standmeet/sdk-core"] --> ASM
    ASM --> LOOP["eino ADK loop
(anthropic native / gemini / openai-compat)"]
    LOOP --> SSE["SSE stream → app (frontend)"]
  end
  subgraph DEV["dev estate — same repo, separate go.mod"]
    EV["eval-harness"] --> ED["EvalDriver
(canned data)"]
  end
  ED --> BUILD["BuildVisitorAgent(driver, input)
(bridge.go: Driver → loop ports)"]
  BUILD --> LOOP
  FUT["runtime promotion
(prod through BuildVisitorAgent — not yet)"] -.-> BUILD

One chat turn (sequence)

sequenceDiagram
  participant A as app (SSE)
  participant R as routes/session
  participant L as eino loop
  participant T as tool (mcp-server / connector)
  A->>R: POST message, SSE opens
  R->>L: RunAgentTurn (capreg-assembled tools)
  loop until final answer
    L-->>A: stream deltas (unbuffered passthrough)
    L->>T: tool call (stdio MCP / connector handle)
    T-->>L: result (+ ui:// card)
  end
  L-->>A: final answer
  Note over R,L: client disconnect does NOT kill the turn (detached)
  L->>R: turn completes → persist messages
  Note over R: persist-at-completion — history lands once, whole
  L-->>A: done, then optional epilogue frame "ghost" (agent_epilogue.go)

Turn lifecycle (state)

stateDiagram-v2
  [*] --> Streaming : turn starts
  Streaming --> Detached : client disconnects
  Streaming --> Completed : final answer
  Detached --> Completed : loop finishes anyway
  Completed --> Persisted : write messages once
  Persisted --> [*] : reload recovers from DB

Children

Related notes