> ## Documentation Index
> Fetch the complete documentation index at: https://f4c7a9e2d8b1-docs.tenzo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Module-owned stage orchestration

> Ownership boundaries, durable state, and workflow coordination for V1 modules

<Warning>
  **Restricted Access**: This documentation is only accessible to @tenzo.ai and @salv.ai email addresses.
</Warning>

## Architecture

Who writes a stage, who does module work, and who follows a link is the
[chosen shape](/internal/modules/orchestrator-contract#chosen-shape).

V1 runtime below still uses today's adapters (`JobWorkflowCoordinator.START_AI_INTERVIEW`,
`TenzoStageOrchestrator`, Resume Review `continue_handoff` activating Interview). Allowed
hops come from the compile. The Admin → Modules walkthrough is unchanged.

Existing application-stage consumers read the `TenzoStage` compatibility projection. The inner click-in canvas is the code-owned `ModuleDefinition` compile (`TenzoStage` hops).

## V1 workflow

V1 authoring is **Admin → Templates Bank → Modules**: named journey templates
as the landing list, then a graph whose cards show each exit already wired
(**Pass → AI Interview**, **Resume Rejected → Stop**). Change a destination
from the dropdown on that exit. Drag a card corner to widen it. A fixed
**Ingest** card sits left of the first module and shows its mapped ATS
stages. Click a module to open the right panel and map ATS stages onto
Ingest or a module start (saved only; not live routing). Hover a module
start label to see that map without opening the drawer.
Double-click a card or choose **Open inner graph** to replace the map with
that module's Tenzo-stage flow; the breadcrumb `{template} / {module}`
returns to the journey map. Click any stage to attach extra actions in the
drawer (email, SMS, ATS note, custom field, ATS stage). Those sit outside
the regular graph and are not extra stages. Outreach cannot take email or
SMS because that stage already reaches out; ATS updates still work there.
A job gets exactly one template. The coordinator
reads the job's snapshot; if Interview is off that snapshot, handoff does not
call Interview `activate`. Until a snapshot exists, the job still compiles
from **AI Resume Review Only**. See
[Job module composition](/internal/modules/composition).

The coordinator starts Resume Review, waits for a handoff-capable terminal result,
evaluates the global gates, and then starts AI Interview when Interview is on
the compiled list. A result that closes or holds the workflow does not start
the next module.

Resume Review and AI Interview are both module-native behind the same
`module_owned_resume_review` flag. When Interview is on the compiled list, the
coordinator still returns `START_AI_INTERVIEW` and the Interview orchestrator
creates the durable run. The existing Tenzo writers (`OUTREACH`,
`SKIP_OUTREACH`, `AI_INTERVIEW_SCHEDULED`, `CALL_COMPLETED`) remain the
compatibility projection. Flag-off enrollment still jumps straight to
`OUTREACH` and does not create an Interview row.

## Two graphs

The journey map is composition only: Ingest is not a card, modules stay compact
(start / continue / end / hold), and Stop is the shared terminal. Card ports
come from the compiled module definition so the outer handles cannot drift from
the click-in.

The click-in is how a module interprets Tenzo stages, grouped by what the
candidate is doing: **Going through**, **Paused**, **Needs a person**, and
**Not continuing**. Resume Review sits on `AWAITING_RESUME_REVIEW`, can leave
through **Pass** (`REVIEW_PASSED`) or end on **Resume Rejected**, and shows
**Re-review** as a hold. AI Interview enters at `OUTREACH` and walks a short
spine through invites, scheduled, and **Call Completed**, with a couple of
pause forks and one dashed line into each destination group. Parks and
stops stay inside the module, not extra journey cards. The click-in already
reads the compiled inner graph. Inner edges are not persisted.

The module run FSM (`pending` / `evaluating` / `passed` on Resume Review,
`pending` / `outreach` / `scheduled` / `completed` on Interview) is lease and
scoring progress only. It is not a sit-able stage language and is not what the
click-in draws. A small run ↔ sit-able map keeps today's module rows working:
Resume Review `pending`/`evaluating` sit on `AWAITING_RESUME_REVIEW`; Interview
`pending` enters at `OUTREACH`.

Allowed module hops come from the compile. Runtime still applies a hop through
`TenzoStageOrchestrator`. Switching that orchestrator onto compiled
`TransitionPlan` execution is a later slice. Inner edges stay compiled, not
persisted, on named journey templates.

## Durable authority

Module state is the authority for progress inside a module. Global workflow state is the authority
for progress between modules. `TenzoStage` remains available as a compatibility projection for
existing UI, filtering, ATS, and automation consumers. Allowed module hops come
from the compile; the run FSM is work progress, not a second hop table.

A projection can combine module and global state when there is no one-to-one legacy stage. Updating
the projection must not create a second workflow authority.

Every local transition writes current module state and append-only module history
atomically. Closing Resume Review results (`rejected`, a rejecting override) also project
onto `TenzoStage` in that same transaction via the compatibility-projection hook, so a
projection CAS miss rolls the module write back.

Continuing handoff (passed / bypassed / passing override → AI Interview) is **not** an
atomic next-module insert. V1 has no separate global workflow row. Resume Review
`continue_handoff` activates the native Interview run, which then writes `OUTREACH` or
`SKIP_OUTREACH` through the existing Tenzo helpers. If that writer call fails, the Resume
Review run stays terminal so a recovery scan can retry: scoring terminals while CCI is
still `AWAITING_RESUME_REVIEW`, and a passing override while CCI is still
`RESUME_REJECTED` or `RE_REVIEW_REQUESTED`. Handoff is therefore eventually consistent,
idempotent, and retryable.

Readers never observe a new local stage without its module-history event. They may briefly
observe a continuing local terminal whose compatibility stage has not yet moved to
`OUTREACH` or `SKIP_OUTREACH`.

## Configuration snapshot

The workflow captures an immutable configuration snapshot when the application enters the module
sequence. Module orchestrators and the coordinator read that snapshot for every attempt and retry.
Edits to live job or organization settings apply to later workflows; they do not change the meaning
of an in-flight decision.

The snapshot contains the versioned inputs needed to reproduce module policy and handoff behavior.
Runtime outputs and lease metadata remain separate from configuration.

## Leases and retries

Runnable work is claimed with a bounded lease. A claim records the worker, lease expiry, and attempt
against the durable module state. Only the current lease holder may publish work for that claim.

If a worker exits or its lease expires, another worker can reclaim the same stage. The retry reloads
the persisted stage and configuration snapshot, then repeats the idempotent operation. A worker
whose lease is stale cannot commit after a newer claim or transition. Delivery count is diagnostic;
it is not workflow state.

External effects use stable idempotency keys derived from the application, module execution, and
effect. A retry may confirm an existing effect, but it must not send or apply it twice.

## Coordinator gates

The workflow coordinator evaluates global gates immediately before a handoff. These gates cover
conditions that apply across modules, such as cancellation, a terminal application decision, or a
global hold. A module reports its local result; it does not bypass these gates or start the next
module directly.

V1 does not have a separate global workflow-history row. A blocked handoff remains inspectable
through the durable module terminal and the current global `TenzoStage`; a later module migration
can add a dedicated cross-module history contract.

## Module contract

What a module may change is [why the split](/internal/modules/orchestrator-contract#why-the-split). Where a continue goes is [endings and custom pathways](/internal/modules/orchestrator-contract#endings-and-custom-pathways).

Resume Review and AI Interview implement activate, claim, transition, and a continue-or-hold terminal. Transition still applies a hop through `TenzoStageOrchestrator`. See [Shared skeleton](/internal/modules/orchestrator-contract#shared-skeleton).

A candidate's place is the module instance, the sit-able stage, and any overlay. The recruiter stage is derived from that place. A move inside the module is decided from the sit-able stage, not from the derived recruiter stage.

V1 modules still also provide:

* a leased, idempotent retry entry point;
* a compatibility projection contribution; and
* today's coordinator-facing result (`START_AI_INTERVIEW` until TEN-1315).

See [Module and stage-orchestrator contract](/internal/modules/orchestrator-contract),
[Resume Review](/internal/modules/resume-review),
[AI Interview](/internal/modules/ai-interview), and
[Job module composition](/internal/modules/composition).
