Document → module

Upload a PDF, get a drafted course · the longest chain in the platform · ← platform hub

In one sentence

An author uploads a document and asks for a course. The platform reads the document, decides what the lessons should be, writes the content of each one with an LLM, generates the pictures, and leaves a draft the author can edit — a process of minutes, driven entirely by events.

Every step is a separate worker, and every worker talks only to authoring-service-v2. Nothing in the chain talks to anything else in the chain, which is why a failure at any stage leaves the run visible and restartable rather than lost.

The chain

sequenceDiagram
  autonumber
  participant A as Author
  participant AV2 as authoring-v2
  participant AM as asset-manager
  participant EW as extraction-worker
  participant CW as content-worker
  participant IW as image-worker
  A->>AV2: upload-document tool
  AV2->>AM: create upload URL
  A->>AM: PUT file to S3
  AM-->>AV2: authoring.asset.upload.completed
  A->>AV2: request-module-from-doc
  AV2-->>EW: authoring.extract.run.requested
  EW-->>AV2: authoring.extract.gates.ready
  EW-->>AV2: authoring.extract.tree.ready
  AV2-->>CW: authoring.content.generation.requested.v3
  CW-->>AV2: authoring.content.generation.ready
  AV2-->>IW: authoring.image.batch.requested
  IW->>AM: upload generated images
  IW-->>AV2: authoring.image.batch.ready

Step by step

  1. Upload. The author picks a file. authoring-v2 asks asset-manager for a presigned URL and the browser uploads straight to S3 — the file never passes through the API. On confirm, asset-manager publishes authoring.asset.upload.completed (asset-manager-service/app/api/v1/assets.py:52), which authoring-v2 consumes (authoring-service-v2/app/consumers/register.py:65).
  2. Start the run. The request-module-from-doc tool registers a run, debits credits, and kicks a durable workflow. Credits are debited up front and refunded if the run fails — see credits & billing.
  3. Extract. authoring-v2 publishes authoring.extract.run.requested (app/services/extraction_orchestration.py:190). extraction-worker parses the document — large PDFs go out to the MinerU parser on Modal via RAGFlow — and answers twice: authoring.extract.gates.ready with what it understood, then authoring.extract.tree.ready with the proposed lesson structure (extraction-worker/app/consumers/run_consumer.py:145, structure_consumer.py:115). Progress arrives continuously on authoring.extract.progress, which is what the author watches in the UI.
  4. Index for retrieval. In parallel the source document is indexed into RAGFlow by rag-context-worker, so the content step can quote the actual document rather than inventing text.
  5. Write the content. For each lesson, authoring-v2 publishes authoring.content.generation.requested.v3 — one message per lesson, carrying every concept plus retrieved evidence. content-worker generates the screens and blocks and writes them directly into authoring-v2's database, then answers authoring.content.generation.ready (authoring-content-worker/app/workers/content_worker.py:588). It owns no database of its own; that shared-table coupling is the strongest seam in the pipeline.
  6. Illustrate. authoring-v2 publishes authoring.image.batch.requested (app/services/tool_dispatcher.py:326). image-worker generates images with Gemini — the batch path rides the Batch API's cheaper 24-hour tier — uploads them to asset-manager, and answers authoring.image.batch.ready. Runs never gate on images: late pictures delay the artwork, not the module.
  7. Draft ready. The author now has an editable draft and can change anything with the authoring tools, then publish it.

The research variant

When there is no source document, the author gives a topic instead. authoring-v2 publishes authoring.research.run.requested (app/tools/handlers/orchestrator_handlers.py:224); research-worker searches the web through OpenRouter and returns candidate topics. The author confirms a selection — a deliberate human gate — and a deeper pass builds the tree. From tree.ready onward the flow rejoins the chain above at step 5.

Where it breaks today

IssueDetail
The non-DBOS fallback never indexes the documentThe fallback path at doc_to_module.py:187 kicks index-document onto authoring.tool-call.requested, a subject the code itself records as having no subscriber (authoring-service-v2/app/tools/scopes.py:8-9). rag-context-worker is not listening, so on that path retrieval has nothing to retrieve. See the authoring-v2 verdicts.
Cancelling an upload does not cancel the parserag-context-worker listens on authoring.rag.indexing.cancel.requested but nothing publishes it. The MinerU parse keeps running and stays billable. Same for document deletion, which never reaches RAGFlow. See rag-context verdicts.
Re-applying the AUTHORING stream definition breaks imagesimage-worker widens the stream's subjects at boot; the checked-in JSON does not include that widening (ARCHITECTURE.md §3.3). Applying the file as-is silently stops image delivery.