Frequently asked questions (FAQ)
Common questions about CocoIndex behavior — how change detection propagates through logic vs inputs, runtime invocation tracking, hidden dependencies, and crash recovery vs cross-target atomicity for target states.
Change detection
Why do logic changes propagate transitively but input changes don’t?
In the call chain foo(a) → bar(b):
- Logic changes propagate: if
bar’s logic changes (code,deps,version), the output offoo(a)could be different too, sofoo’s memo must be invalidated. - Input changes don’t propagate:
bis the result of applying part offoo’s logic toa. As long asfoo’s logic andaare unchanged,bwon’t change — there’s nothing to propagate.
How does logic change propagation work?
Logic changes propagate based on runtime invocations, not static call graphs. Two consequences:
- Unannotated functions don’t break the chain. If
f1()→f2()→f3(), andf1andf3are decorated with@coco.fnbutf2is not, a logic change inf3still invalidatesf1’s memo. - Conditional calls are tracked precisely. If
f1()callsf2()only in one branch, then invocations off1()that didn’t callf2()are not invalidated whenf2’s logic changes — only invocations that actually calledf2()are affected.
What about hidden dependencies like global variables or files?
Like any memoization system (e.g., @functools.cache), CocoIndex’s change detection assumes functions depend only on their declared inputs. If a function reads a global variable, a file, or external state not passed through arguments, changes to those won’t be detected automatically.
CocoIndex provides mechanisms to capture some of these dependencies:
deps— declares module-level values (like a prompt string or model name) as part of the function’s logic. Changes to these values invalidate dependent memos, just like any other logic change. Note:depsis snapshotted once at decoration time.use_context()— retrieves shared resources viaContextKey. Withdetect_change=True, changes to the provided value invalidate dependent memos.
For per-call values that change at runtime, pass them as regular function arguments instead.
Target states and syncing
What happens if my pipeline crashes mid-update?
CocoIndex’s internal state is always consistent — even after a crash or kill -9. On the next app.update(), CocoIndex automatically recovers: it computes the current desired state and reconciles against all possible previous states, converging the target to the correct state. No manual cleanup is needed. See Error Handling — Interrupted updates and recovery for details.
Are target state writes transactional across targets?
Not across targets. When a processing component finishes, CocoIndex sends all its target state changes to each target backend as a unit — all writes happen after processing completes, never partially during execution. Each target backend applies its batch atomically when supported (e.g., within a database transaction). But changes across different target backends (e.g., Postgres and local files) are not transactional with each other. See How target states sync for details.