| name | process-book |
| description | Guide for working with the process_book job in the shelf book digitization pipeline. Use when modifying, debugging, or understanding the job orchestration patterns, work unit lifecycle, state management, or when adding new pipeline stages. Specific stages may change over time; this skill focuses on durable patterns. |
Working with process_book
The process_book job orchestrates the book digitization pipeline using a work unit pattern. All stages are consolidated into this single job - there are no separate job packages.
Quick Reference
Key Locations
internal/jobs/process_book/
├── process_book.go # Package entry, NewJob(), factory
└── job/
├── job.go # Job struct, Start(), OnComplete()
├── types.go # WorkUnitInfo, constants
├── state.go # MaybeStartBookOperations(), triggers
├── finalize.go # Finalize-ToC inline (discover, gap analysis)
├── structure.go # Common-structure inline (extract, polish)
├── link_toc.go # ToC linking agents
└── [stage].go # One file per stage
internal/jobs/common/
├── state.go # BookState, PageState, OperationState
├── load.go # LoadBook()
├── persist.go # Persistence helpers (async-first)
├── reset.go # Reset helpers for crash recovery
└── [helpers].go # Shared utilities
Core Patterns
Work Unit Lifecycle
unit := j.CreateXWorkUnit(ctx, pageNum, state)
j.RegisterWorkUnit(unit.ID, WorkUnitInfo{...})
func (j *Job) OnComplete(ctx, result) {
info, _ := j.GetWorkUnit(result.WorkUnitID)
switch info.UnitType {
case WorkUnitTypeX:
return j.HandleXComplete(ctx, info, result)
}
}
j.RemoveWorkUnit(result.WorkUnitID)
State Persistence (Async-First)
state.SetComplete(true)
sink.Send(defra.WriteOp{...})
result, _ := sink.SendSync(ctx, op)
j.TocDocID = result.DocID
Important: Agent state persistence is fully async. No intermediate saves during agent loops - crash recovery restarts agents from scratch.
Cascading Triggers (state.go)
func (j *Job) MaybeStartBookOperations(ctx) []jobs.WorkUnit {
if j.SomeThreshold() && j.Book.SomeOp.CanStart() {
j.Book.SomeOp.Start()
return []jobs.WorkUnit{j.CreateSomeWorkUnit(ctx)}
}
}
OperationState API
op.CanStart()
op.Start()
op.Complete()
op.Fail(max)
op.IsDone()
Stage Categories
| Category | Pattern | State | Example |
|---|
| Page-level | Per-page parallel | PageState | OCR, blend, label |
| Book-level | Threshold trigger | OperationState | Metadata |
| Agent-based | Multi-turn loop | Agent struct (no intermediate persist) | ToC finder, chapter finder |
| Inline sub-job | Embedded in parent | Sub-job state | Finalize, Structure |
See stages.md for implementation patterns.
Debugging
Via API
shelf api jobs status <book-id>
shelf api books get <book-id>
shelf api agent-logs list --job-id X
Via DefraDB
{ Page(filter: {book_id: {_eq: "<id>"}}) { page_num ocr_complete } }
{ Book(filter: {_docID: {_eq: "<id>"}}) { metadata_complete toc_finalize_complete } }
{ AgentState(filter: {book_id: {_eq: "<id>"}}) { agent_id agent_type complete } }
See debugging.md for full query reference.
Common Tasks
Add a new stage
- Define
WorkUnitTypeX constant in types.go
- Add state fields to PageState or BookState
- Create handler file with
CreateX and HandleXComplete
- Add case to
OnComplete() switch
- Add trigger in handler or
MaybeStartBookOperations()
- Add persistence helper in common/persist.go (use async
SendToSink)
See adding-stages.md for full guide.
Debug a stuck stage
- Query DB for precondition states
- Check
*_started / *_complete flags
- Enable debug:
Config.DebugAgents = true
- Check agent logs if agent-based
Agent state and crash recovery
- Agent states are persisted once at creation (async)
- No intermediate saves during the agent loop (performance optimization)
- On crash, agents restart from scratch (acceptable tradeoff)
- Use
DeleteAgentStateByAgentID for cleanup (not DocID-based)