@reactive-agents/interaction is a standalone package for building human-in-the-loop control around an agent: named interaction modes, milestone checkpoints, an approval-gate primitive, real-time collaboration sessions, and a learner that adapts to a user’s approval patterns over time.
Install
Section titled “Install”npm install @reactive-agents/interaction# orbun add @reactive-agents/interactionThe five modes
Section titled “The five modes”type InteractionModeType = | "autonomous" // Fire-and-forget: agent runs independently | "supervised" // Checkpoints: agent pauses at milestones for approval | "collaborative" // Real-time: agent and user work together | "consultative" // Advisory: agent observes and suggests | "interrogative" // Drill-down: user explores agent state/reasoningModeSwitcher tracks the active mode per agent and evaluates transition rules (e.g. escalate from autonomous to supervised after N consecutive errors) via InteractionConfig’s modeTransitionRules / escalationConditions.
Provide the layer
Section titled “Provide the layer”createInteractionLayer(config?) wires all five services (InteractionManager, ModeSwitcher, NotificationService, CheckpointService, CollaborationService, PreferenceLearner) into one Layer. It requires EventBus from @reactive-agents/core.
import { Effect } from "effect"import { createInteractionLayer, InteractionManager } from "@reactive-agents/interaction"import { EventBusLive } from "@reactive-agents/core"
const program = Effect.gen(function* () { const interaction = yield* InteractionManager yield* interaction.switchMode("agent-1", "supervised")})
await Effect.runPromise( program.pipe( Effect.provide(createInteractionLayer()), Effect.provide(EventBusLive), ),)InteractionManager — the unified facade
Section titled “InteractionManager — the unified facade”Reach for InteractionManager first; it delegates to the other four services so you rarely need to depend on them individually.
| Method | Backed by | Does |
|---|---|---|
getMode(agentId) / switchMode(agentId, mode) | ModeSwitcher | Read/set the active interaction mode |
evaluateTransition(agentId, context) | ModeSwitcher | Check the configured transition/escalation rules against run context |
notify(params) / listUnread() / markRead(id) | NotificationService | Send and track notifications across channels |
createCheckpoint(params) / resolveCheckpoint(id, status, comment?) / listPendingCheckpoints(agentId) | CheckpointService | Pause at a milestone, resolve it with a human decision |
startCollaboration(params) / endCollaboration(id) / sendCollaborationMessage(params) | CollaborationService | Real-time back-and-forth session between agent and user |
getPreference(userId) / shouldAutoApprove(params) | PreferenceLearner | Read a learned approval pattern; decide whether to skip a prompt |
approvalGate(action, timeoutMs?) | InteractionManager | Suspends the calling fiber, publishes an approval-requested event carrying a generated gate id, and resolves to an ApprovalResult once something calls resolveApproval with that id (or the timeout elapses) |
resolveApproval(gateId, approved, reason?) | InteractionManager | Called from elsewhere (a UI, a CLI prompt, a webhook) with the gate id read off the approval-requested event, to unblock the waiting approvalGate call |
import { Effect } from "effect"import { InteractionManager } from "@reactive-agents/interaction"
// The agent-side fiber: suspends until resolved or timed out.const requestDeletion = Effect.gen(function* () { const interaction = yield* InteractionManager const result = yield* interaction.approvalGate("delete-production-file", 30_000) return result.approved // false on denial or timeout; check result.timedOut / result.reason})
// Elsewhere — a handler subscribed to the "approval-requested" EventBus event,// reading { gateId, action } off its payload:const respondToApproval = (gateId: string) => Effect.gen(function* () { const interaction = yield* InteractionManager yield* interaction.resolveApproval(gateId, true, "confirmed by reviewer") })Preference learning
Section titled “Preference learning”PreferenceLearner observes approval/denial history per user (ApprovalPattern) and shouldAutoApprove uses it to skip a prompt once a pattern is confident enough, tuned by InterruptionTolerance. This is a local, in-process heuristic — not the same mechanism as the reactive-intelligence calibration store.