> ## 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.

# Job module composition

> How a job gets its journey snapshot, and where you author named templates

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

Handoff and hops follow the
[module and stage-orchestrator contract](/internal/modules/orchestrator-contract):
outputs do not name the next module; composition links do. This page is how a job
gets its snapshot. It does not change Admin → Modules chrome.

## What composition is

A job's module composition is the ordered list of module kinds that job runs,
plus the terminal-out connections between them. Resume Review and AI Interview
are the V1 kinds. Ingest is not a module: it is the admit-and-park pipe that
always sits in front of the first authored module.

Author named journey templates on **Admin → Templates Bank → Modules**. The
landing view is the template list. Opening one is a graph canvas: a bank of
modules on the left and a pannable, zoomable map in the rest of the page.
**Full screen** expands the same editor to the viewport. Cards show the
module name, a one-line purpose, and each exit already labeled with its next
step (**Pass → AI Interview**, **Resume Rejected → Stop**). Change that
destination from the dropdown on the exit. You can still drag a handle.
Drag a card corner to widen it so the labels stay on one line. Click the
card body to open a right **panel** in the same workspace. Double-click or
**Open inner graph** replaces the map with that module's Tenzo-stage flow.
Click a stage card to open its drawer. Every stage can take **extra
actions** that fire outside the regular graph — send email, send SMS, write
an ATS note, update a custom field, or update the ATS application stage.
Those are not extra stages. **Outreach** cannot take email or SMS because
that stage already reaches out; ATS updates still work there. Extra
actions stay on the click-in for now; they are not saved on the template
and do not run.
Resume Review has **Awaiting Review** on the left (start), **Pass** and
**Resume Rejected** on the right (end stages), and **Re-review** on the card
(hold — it does not leave the module). AI Interview has **Outreach** in and
**Call Completed** out. Those handles come from the compiled inner graph, not
from hardcoded card copy. Parks, accommodations, and Opted Out stay inside
the click-in, not extra journey cards. Each kind can appear once. **Save**
persists the template. Bank edits do not change in-flight jobs until you
reassign.

The system **Default journey** is read-only (Resume Review continue → AI
Interview → Stop, Resume Review end → Stop). Duplicate it to make a copy.

A fixed **Ingest** card sits left of the first module. It is not in the bank
and has no remove control. The solid edge is Ingest → first module start.
Dotted Ingest edges to other starts are a preview of saved start maps, not
live routing. Mapped ATS stages show on the Ingest card. Hover a module
start label to see that module's map; edit either map from the drawer.

On Ingest and each module start, the drawer can map ATS stages. Those maps
are saved on the template. They are **not live**: candidates still enter at
the first module after Ingest. Empty is not "all ATS stages". A stage can
map to only one start on that template, including Ingest.

Do not say "Campaign" in that UI. The Templates Bank item is **Modules**.

## Job assignment

A job gets **exactly one** journey. Assignment order is job override → parent
job template → integration rule (`Assign Journey Template`) → system Default.

On **AI Interview Behavior → Candidate journey**, pick inherit or override,
then **Open** to preview the snapshot on that job. The first snapshot for a
new job compiles today's **AI Resume Review Only** toggle when the job
inherits Default. After that, the snapshot is the runtime list.

| Source                                     | Runtime list                       |
| ------------------------------------------ | ---------------------------------- |
| Snapshot present                           | Modules on that snapshot, in order |
| No snapshot yet, AI Resume Review Only off | Resume Review → AI Interview       |
| No snapshot yet, AI Resume Review Only on  | Resume Review                      |

## What this is not

This slice does not ship:

* backend start-stage jumping (`resolve_post_ingest_module_start`)
* persisted or executed extra actions (the click-in picker is authoring-only)
* recruiter stage renames (Resume Rejected / Failed Knockout stay as they are)
* a separate Journey Templates page
* a persisted `INGEST` module row
* TenzoStageOrchestrator executing the compile (later slice)

Those belong to later TEN-925 work. See [Module-owned stage orchestration](/internal/modules/overview).

## Compiler

`compile_v1_composition(interviews_enabled=...)` is a pure function used only
when no campaign journey snapshot exists. Resume Review is always first. AI
Interview is included only when interviews are enabled
(`ai_resume_review_only_settings.enabled` is false). Identity and display names
come from the pipeline `ModuleDefinition` registry. Allowed inner hops still
come from the pipeline compile, not this sequence helper.

A snapshot, when present, compiles through `composition_from_graph` with
source `journey_snapshot`. The coordinator reads that list. The frontend
compile helper uses the same keys and display names (`resume_review`,
`ai_interview` / Resume Review, AI Interview) so the map and the backend type
stay aligned. The job preview map is a visual overlay only; it uses the same
inner-graph compile for card ports and click-in, and does not author inner
edges.

## Handoff reads composition

When the flag is on and a snapshot omits Interview, Resume Review
`continue_handoff` does **not** call Interview `activate`. In-flight runs do
not re-fire on save.

When there is no snapshot, resume-only jobs still get a bypassed Interview
run so they project to `SKIP_OUTREACH`. See [AI Interview module](/internal/modules/ai-interview).

## Recruiter list

Composition does not change `TenzoStage`. Parks stay overlays. The list, filters,
and ATS automation keep reading the compatibility projection.
