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
| Dimension | Standard session | Durable workflow |
|---|---|---|
| Execution host | donmai daemon runner | Inngest (cloud) or BullMQ (self-hosted) |
| Restart safety | Redis state for in-flight sessions | Step memoisation - completed steps never re-run |
| Wait for event | Not supported | step.waitForEvent with timeout |
| Delay / sleep | Not supported | step.sleep("30m") |
| Trigger | Webhook / workflow node dispatch | Cron string or named Inngest event |
| Use case | Developer sessions, SDLC runs | PM 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 IDstepId- the step namenodeId- the workflow function IDaction- the event or function nameorgId,workspaceId- for row-level security
This provides an audit trail of every memoised step across all workflow retries, visible in the admin observability surface.
Related pages
- PM Agents - the two live PM-agent archetypes built on durable workflows
- Agent Cards - Agent Cards referenced by durable workflows
- Workflow Gates - the workflow-editor counterpart for human approval gates
- SDLC Default Template - standard session-based SDLC (not durable)