|
|
3 ماه پیش | |
|---|---|---|
| .. | ||
| src | 3 ماه پیش | |
| tests | 3 ماه پیش | |
| README.md | 3 ماه پیش | |
| package.json | 3 ماه پیش | |
| tsconfig.json | 3 ماه پیش | |
THE concrete agent plugin: ReactLoopAgent and the loop driver. Implements the Agent interface and drives the session/turn/step lifecycle.
This is the only package in the harness that contains concrete loop logic. Everything else is an abstract service or a plugin against extension seams — new behavior goes into plugins, not here.
AgentLoop (ctx key: agentLoop)ctx.agentLoop.create(id: string, options?: AgentOptions): ReactLoopAgent — config-driven create: an agent on a fresh per-run session id ${id}-session-<uuid> (no cwd). Used for cordis.yml-configured agents. The per-run uuid avoids colliding with the on-disk log a prior run materialized once a durable persistence backend is loaded; each run is a new session (a deliberate demo simplification — a real resume-or-create policy is a TODO). Disposed with the calling fiber.AgentLoop also implements the AgentFactory seam and registers itself via ctx.agents.setFactory(this), so plugins create/resume agents through ctx.agents (the interface):
ctx.agents.create({ agentId, sessionId, meta?, agentOptions? }) — programmatic create on a caller-supplied sessionId (e.g. an ACP-generated id), NOT ${id}-session.ctx.agents.resume({ agentId, resumeSessionId, agentOptions? }) — load a persisted session via ctx.sessionPersistence (session persistence) and resume an agent on it. The live session id is the resumed id; turn numbering and derived history continue from the loaded log. Requires a session-persistence backend (NOT hard-injected — non-persistent demos still work; resume rejects with a clear error when persistence is absent).agents, sessions, llm, tools, systemPrompt — all five interface services.
interface Config {
agents: Array<{
id: string // required
model?: string
systemPrompt?: string
}>
}
Agents listed in config are auto-created at startup.
ReactLoopAgent — the concrete Agent implementation. Owns the inbox (Inbox), the per-step AbortController, and the loop driver. Everything observable happens through session events and the agent/* event taxonomy.Inbox — per-agent queued + steering FIFOs (enqueue, steer, drainQueued, drainSteering, waitForQueued).loop.ts)One invocation of runLoop() drives one agent for its whole lifetime:
forever:
wait for queued messages (idle)
TURN (error-contained):
drain queued → 'turn/start' → session('user/message')
STEP loop:
drain steering
assembly = systemPrompt.assemble()
request = waterfall agent/request
stream llm.stream(request) → session('assistant/chunk')
message = waterfall agent/step-result
session('assistant/message')
each tool-call: session('tool/call') → tools.execute() → session('tool/result')
drain steering → session('steering/message')
cont = waterfall agent/turn-continuation
if !cont: break
session('turn/end')
await session/flush
re-enqueue leftover steering as queued
idle unless more queued
Error containment: a throwing plugin ends the turn, never the loop. Dispose mid-turn emits agent/status('disposed') and ends with reason disposed. A step that hits the model's output-token ceiling makes the turn end max-tokens (the rule: any max-tokens step in the turn surfaces as max-tokens; disposed/aborted/error still take precedence) — distinct from a clean completed stop.
Everything that goes beyond "call the model, run the tools, repeat" belongs to plugins listening on the event taxonomy:
agent/request, agent/step-result, tools/execute, agent/turn-continuationagent/requesttools/executeAgentLoop.create()session/event + session/flushagent/stream-chunk + agent/* events