Rensei docs
Agent Cards

Long-Running Agents

Inngest/BullMQ durable workflows for long-running agents.

Partial feature. The DurableWorkflow abstraction and the Inngest adapter are implemented and registered (the PM-agent workflows run on them), but the PM agents' effect layer is still stubbed - their agent-dispatch and Linear-apply steps log instead of acting (see PM Agents). The BullMQ adapter is coded against the same interface but unwired: nothing selects it at runtime and it has no production callsite.

Standard Rensei sessions are bounded by the daemon's execution window. Long-running agents - PM agents, audit workflows, cross-project memory consolidation - need to survive server restarts, wait for external events with multi-hour timeouts, and retry individual steps without replaying the whole workflow. The DurableWorkflow abstraction provides these guarantees through a provider-agnostic interface.

How durable workflows differ from standard sessions

DimensionStandard sessionDurable workflow
Execution hostdonmai daemon runnerInngest (cloud) or BullMQ (self-hosted)
Restart safetyRedis state for in-flight sessionsStep memoisation - completed steps never re-run
Wait for eventNot supportedstep.waitForEvent with timeout
Delay / sleepNot supportedstep.sleep("30m")
TriggerWebhook / workflow node dispatchCron string or named Inngest event
Use caseDeveloper sessions, SDLC runsPM agents, scheduled reviews, multi-stage audits

The DurableWorkflow interface

Every durable workflow is defined as a DurableWorkflow<TInput, TOutput> object:

import type { DurableWorkflow, DurableWorkflowContext } from '@/lib/durability/types'

interface MyAgentInput {
  orgId: string
  projectId: string
  since: string
}

export const myAgentWorkflow: DurableWorkflow<MyAgentInput> = {
  // Stable id - used as the Inngest function id and DB key
  id: 'rensei/my-agent.run',

  // Trigger: cron or named event
  trigger: { cron: '0 9 * * 1' },          // every Monday 09:00 UTC
  // or: trigger: { event: 'rensei/my-agent.trigger' },

  async run(ctx: DurableWorkflowContext, input: MyAgentInput) {
    // Step 1 - memoised: runs once, result replayed on retry
    const issues = await ctx.step.run('fetch-issues', async () => {
      return fetchLinearIssues(input.orgId, input.since)
    })

    // Step 2 - wait for a human action with a 24-hour timeout
    const approval = await ctx.step.waitForEvent('await-approval', {
      event: 'rensei/approval.resolved',
      match: 'data.workflowRunId',
      timeout: '24h',
    })

    if (!approval) {
      // Timed out - handle gracefully
      return { status: 'timed-out' }
    }

    // Step 3 - sleep before sending a follow-up
    await ctx.step.sleep('cooldown', '30m')

    const result = await ctx.step.run('send-summary', async () => {
      return sendSummaryComment(issues, approval)
    })

    return { status: 'completed', issueCount: issues.length }
  },
}

Step tools

Prop

Type

Registering a workflow

Implement the workflow body in src/lib/inngest/functions/pm-<name>.ts. Export the workflow object and the trigger event name constant.

Register it in src/lib/inngest/functions.ts - both the cron trigger and the manual trigger event.

Append a PmAgentRegistration to PM_AGENT_REGISTRATIONS in src/lib/pm-agents/registered.ts (for PM-agent archetypes) or register directly with the Inngest client (for non-PM workflows).

PM session completion bridge

When a durable workflow dispatches a Rensei agent session as a step.run, the PM session completion bridge (pm-session-completion.ts) fires a rensei/pm.agent-session.completed event when the inner session reaches a terminal state. This allows the outer durable workflow to resume via step.waitForEvent after the agent session completes:

// Inside a durable workflow
const sessionResult = await ctx.step.waitForEvent('session-completed', {
  event: 'rensei/pm.agent-session.completed',
  match: 'data.sessionId',
  timeout: '2h',
})

The bridge derives the set of PM agent observationAgentId values from PM_AGENT_REGISTRATIONS to know which sessions to watch.

Inngest adapter

The default adapter wraps @inngest/nextjs. Workflows are registered as Inngest functions and served from /api/inngest. No additional infrastructure is required beyond the INNGEST_EVENT_KEY and INNGEST_SIGNING_KEY environment variables.

# Required environment variables
INNGEST_EVENT_KEY=signkey-prod-...
INNGEST_SIGNING_KEY=signkey-prod-...

Inngest provides its own dashboard at app.inngest.com for monitoring run history, retrying failed steps, and inspecting payloads.

BullMQ adapter (coded, unwired)

For on-premises and BFSI deployments where data cannot leave the customer network, a BullMQ adapter exists in the codebase. It satisfies the same DurableWorkflow interface, so no call-site changes would be required to adopt it.

The BullMQ adapter is not selectable today: workflow registration hardcodes the Inngest adapter, there is no adapter-selection configuration, and no worker bootstrap or enqueue path is wired. TLS/Sentinel/Cluster Redis configuration, cross-process worker management, Dead Letter Queue routing, and dashboard integration are also deferred. Contact your Rensei account team if you need on-premises durability.

Step execution audit

Every step.run call writes a step_executions row with:

  • instanceId - the Inngest run ID
  • stepId - the step name
  • nodeId - the workflow function ID
  • action - the event or function name
  • orgId, workspaceId - for row-level security

This provides an audit trail of every memoised step across all workflow retries, visible in the admin observability surface.

On this page