Skip to content

[Feature] agents: AgentActor — event-sourced conversation loop #424

Description

@pathosDev

Part of the agents epic #421. The centerpiece of the module.

Rationale

An agent session is an append-only conversation log — exactly what PersistentActor models. Event sourcing gives audit (tokens/cost per call), replayable tests, and mid-turn crash recovery: the intent (tool-call-requested) is persisted before tools execute, so a restarted agent resumes deterministically instead of losing the turn or silently re-running side effects.

Scope

  • AgentActor extends PersistentActor<AgentCommand, AgentEvent, AgentState>; persisted event union with kind literals (user-message, assistant-message, tool-call-requested, tool-result, turn-completed, turn-failed, context-compacted, approval-requested, approval-resolved, handed-off) + codecs with EventAdapter versioning from day one.
  • Turn loop: guardrails check → context strategy → model call (streamed via self-tell pump with delta coalescing, mailbox-safe) → persist assistant message + tool intents → invoke tools via ToolRegistry → loop until finish (maxIterationsPerTurn bound).
  • Recovery policies: interruptedTurn: 'retry-model' | 'fail-turn'; only idempotent: true tools are re-invoked; others fail or park the turn.
  • TruncateStrategy as the first ContextStrategy (summarization is a follow-up; compaction results are persisted as events so replay stays stable).
  • Snapshots via everyNEvents + opt-in destructive compaction through the existing Journal.delete (documented trade-off: storage vs. audit/replay).
  • AgentsExtension (model registry, default ToolRegistry) via the Extension pattern; AgentOptions.ts per the AGENTS.md options family (AgentOptionsType / AgentOptionsBuilder / AgentOptions / AgentOptionsValidator; resolution explicit > HOCON actor-ts.agents.* > defaults). Engine match arms delegate to private onXxx handlers.
const agentOptions = AgentOptions.create()
  .withAgentId('support-42')
  .withModel('default')
  .withSystemPrompt('You are a helpful support agent.')
  .withTools(['get_weather'])
  .withGuardrails({ maxIterationsPerTurn: 6, tokenBudget: { perSession: 200_000 } });
const support = system.spawn(Props.create(() => new AgentActor(agentOptions)), 'support-42');

support.tell({ kind: 'user-turn', turnId: 'T1', content: 'Weather in Kiel?', streamTo: uiRef });
const result = await support.ask<TurnResult>({ kind: 'user-turn', turnId: 'T2', content: 'Thanks!' });

Documentation

Docs-chapter core pages (quickstart, event model, recovery semantics, config reference — EN + DE); JSDoc; CHANGELOG; feeds the examples/agents/ app (tracked in the epic).

Acceptance

  • Full-turn tests incl. streaming + tool loop under ScriptedChatModel/ManualScheduler; kill-mid-turn recovery tests for both policies; guardrail stop conditions; snapshot/recovery round-trip.

Non-goals

Summarize strategy, typed-facade polish, approval gate, orchestration helpers (separate issues).

Relates

Epic #421; depends on the ChatModel and ToolRegistry issues.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestpriority: highTop priority — high impact, plan next

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions