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.
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.
| Task | Reference | |------|-----------| | Understand pipeline architecture | architecture.md | | Stage implementation patterns | stages.md | | Debug issues | debugging.md | | Add/modify stages | adding-stages.md |
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
// 1. Create and register
unit := j.CreateXWorkUnit(ctx, pageNum, state)
j.RegisterWorkUnit(unit.ID, WorkUnitInfo{...})
// 2. Worker executes (external)
// 3. Handle completion
func (j *Job) OnComplete(ctx, result) {
info, _ := j.GetWorkUnit(result.WorkUnitID)
switch info.UnitType {
case WorkUnitTypeX:
return j.HandleXComplete(ctx, info, result)
}
}
// 4. Clean up
j.RemoveWorkUnit(result.WorkUnitID)
// Async writes (default - non-blocking, fire-and-forget)
state.SetComplete(true) // In-memory first
sink.Send(defra.WriteOp{...}) // Async to DB
// Sync only for critical creates (need DocID back)
result, _ := sink.SendSync(ctx, op) // Blocking
j.TocDocID = result.DocID
Important: Agent state persistence is fully async. No intermediate saves during agent loops - crash recovery restarts agents from scratch.
func (j *Job) MaybeStartBookOperations(ctx) []jobs.WorkUnit {
if j.SomeThreshold() && j.Book.SomeOp.CanStart() {
j.Book.SomeOp.Start()
return []jobs.WorkUnit{j.CreateSomeWorkUnit(ctx)}
}
// ... more triggers
}
op.CanStart() // true if not started
op.Start() // NotStarted → InProgress
op.Complete() // InProgress → Complete
op.Fail(max) // Increment retry, maybe → Failed
op.IsDone() // Complete or permanently Failed
| 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.
shelf api jobs status <book-id> # Stage progress
shelf api books get <book-id> # Book state
shelf api agent-logs list --job-id X # Agent logs (if debug enabled)
{ 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.
WorkUnitTypeX constant in types.goCreateX and HandleXCompleteOnComplete() switchMaybeStartBookOperations()SendToSink)See adding-stages.md for full guide.
*_started / *_complete flagsConfig.DebugAgents = trueDeleteAgentStateByAgentID for cleanup (not DocID-based)Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer