inb-21: drop model-escalation chain; pin single gemma4:e4b model; add PR CI #1
Reference in New Issue
Block a user
Delete Branch "inb-21-drop-model-chain"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
INB-21 board decision: the pipeline makes a single LLM call per
step. There is no model-escalation chain. The model is pinned via
INBOXOS_LLM_MODEL(classify + everything not routed by theextract prefix) and
INBOXOS_LLM_MODEL_EXTRACT(
inboxos:extract-*only); both default togemma4:e4b.Transient HTTP / JSON failures are retried by tenacity (3
attempts, exponential backoff) inside
llm_run_command; apermanent failure dead-letters the message and the next
scheduled run retries from the same checkpoint.
Why
The chain was added on the assumption that
gemma4:e4bwouldbe flaky on rich emails and that escalating to
gemma4:12b/gemma4:31b-cloudwould trade wall-clock latency forcompletion rate. In practice the
gemma4:e4bextract stepsucceeds in <30s for the Substack input that previously
dead-lettered in live run #674 (UID 4017, 86KB body) once
INBOXOS_LLM_MAX_TOKENSwas raised to 4096. The chain neverescalates in production because the cheap primary always
succeeds; it only adds dead code and a confusing config knob.
Tenacity in
llm_run_commandis the right retry policy —same-model retry on transient failures, not a 3x retry of the
same broken call before escalation.
Changes
pipelines/common.py— dropmodel_chain/INBOXOS_LLM_MODEL_CHAINsupport; dropollama_run_command_with_chain/llm_run_command_with_chain. UpdateLLMConfigdocstringto spell out the single-model contract.
pipelines/historical_pipeline.py—classifyandextract_for_modulecallllm_run_commanddirectly.pipelines/live_pipeline.py— same..gitea/workflows/{live,historical}-pipeline.yml— dropINBOXOS_LLM_MODEL_CHAINfrom the env block; refreshheader comments.
pipelines/_tests/smoke_inb21_extract.py— assertgemma4:e4bfor both classify and extract; callllm_run_commanddirectly.pipelines/_tests/test_live_pipeline_offline.py—defaults use
gemma4:e4bfor both; drop thefake_chaininstall in
_install_fakes.pipelines/_tests/test_historical_pipeline_offline.py—drop
test_process_escalation_recovers_on_second_model;the 100MB e2e uses the single-model fake.
.gitea/workflows/ci-pr.yml(new) — PR-only CI gate.Three jobs:
fast-tests(gitea_request, mbox_stream,live_pipeline_offline, historical fast subset),
e2e-100mb(the slow historical end-to-end against asynthetic 100MB mbox), and
live-llm-smoke(real Ollamaagainst the Substack input from run #674). Triggers on
pull_requesttargetingmain+workflow_dispatch.live-llm-smokeis a no-op whenOLLAMA_BASE_URLisunset so fork PRs without infra access stay unblocked.
Operational contract (per CEO directive, 2026-06-07)
Run fast tests locally, commit, push, open a PR, wait for the
ci-prworkflow to go green, then merge. The 100MB e2e andlive Ollama smoke no longer run on a developer workstation.
Acceptance criteria
end-to-end (IMAP fetch → EML backup → classify →
extract → UPSERT in NocoDB by Message-ID). Verified
locally against the run #674 Substack input via
smoke_inb21_extract.pywithgemma4:e4b— theprevious
finish_reason: lengthis no longerreproduced and the JSON record preserves the
Message-ID and a non-empty
Contact_Email.extract_permanent_failureevents on the nextscheduled live run. The chain never escalated, the
cheap primary succeeded, the fix is a code-path
reduction (less surface area to fail).
Test plan
test_live_pipeline_offline.py(14/14)test_gitea_request_offline.py(10/10)test_mbox_stream.py(9/9)test_historical_pipeline_offline.pyfast subset(7/7)
e2e_100mb_synthetic_mbox— runs in the newci-prworkflow, not locally.smoke_inb21_extract.pyagainst live Ollama —runs in the new
ci-prworkflow, not locally.Refs INB-21,
INB-17.
INB-21 board decision: the pipeline makes a single LLM call per step, with the model pinned via INBOXOS_LLM_MODEL / INBOXOS_LLM_MODEL_EXTRACT (default gemma4:e4b for both). There is no model-escalation chain. Transient HTTP / JSON failures are retried by tenacity in llm_run_command (3 attempts, exponential backoff); a permanent failure dead-letters the message and the next scheduled run retries from the same checkpoint. Changes ------- * pipelines/common.py - LLMConfig: drop model_chain field and the INBOXOS_LLM_MODEL_CHAIN env-var loader; drop model_chain_for_command. - Drop ollama_run_command_with_chain and llm_run_command_with_chain. - Update docstring to spell out the single-model contract and the retry policy. * pipelines/historical_pipeline.py - classify() and extract_for_module() now call llm_run_command directly (no chain). - main() summary log no longer includes the chain field. * pipelines/live_pipeline.py - extract_for_module() now calls llm_run_command directly. - Drop the now-unused llm_run_command_with_chain import. * .gitea/workflows/{live,historical}-pipeline.yml - Drop INBOXOS_LLM_MODEL_CHAIN from the env block. - Refresh the header comments to describe the single-model contract. * pipelines/_tests/smoke_inb21_extract.py - Now asserts the model is gemma4:e4b (the INB-21 default), not the previous lfm2.5:8b / gemma4:12b split. - Calls llm_run_command directly (no chain wrapper). * pipelines/_tests/test_live_pipeline_offline.py - Default cfg now uses gemma4:e4b for both classify and extract; the per-step routing test asserts the new defaults. - Drop the fake_chain install in _install_fakes; drop the lp.llm_run_command_with_chain patch. * pipelines/_tests/test_historical_pipeline_offline.py - Drop test_process_escalation_recovers_on_second_model (chain is gone). - _install_fakes now patches llm_run_command instead of llm_run_command_with_chain; the 100MB e2e uses the same single-model fake. * .gitea/workflows/ci-pr.yml (new) - PR-only gate. Three jobs: fast-tests (gitea_request, mbox_stream, live_pipeline_offline, historical fast subset), e2e-100mb (the slow historical end-to-end against a synthetic 100MB mbox), and live-llm-smoke (real Ollama against the Substack input from run #674). Triggers on pull_request targeting main + workflow_dispatch for manual reruns. live-llm-smoke is no-op when OLLAMA_BASE_URL is unset so fork PRs without infra access stay unblocked. Operational contract (per CEO directive, 2026-06-07) ---------------------------------------------------- Run fast tests locally, commit, push, open a PR, wait for the ci-pr workflow to go green, then merge. The 100MB e2e and live Ollama smoke no longer run on a developer workstation. Co-Authored-By: Paperclip <noreply@paperclip.ing>The e2e_100mb test calls hp.main([]) which calls from_env() on S3Config, NocoConfig, LLMConfig, and HistoricalConfig. Each of those requires env vars (SEAWEEDFS_S3_*, NOCODB_*, etc.). On a local machine the test runner's main() helper happens to call _setup_env() at the top, but when invoked directly via python - <<'PY' from test_historical_pipeline_offline import test_e2e_100mb_synthetic_mbox test_e2e_100mb_synthetic_mbox() PY (as the PR CI workflow does) the helper is bypassed and the test fails with config_error: SEAWEEDFS_S3_BUCKET is not set. Fix: call _setup_env() inside the e2e test itself, so it works under any runner (unittest, pytest, heredoc, CI, ad-hoc). Co-Authored-By: Paperclip <noreply@paperclip.ing>Re-trigger: ci-pr run #740
Root cause of run #730 failure.
test_e2e_100mb_synthetic_mboxcallshp.main([]), which in turn callsfrom_env()onS3Config,NocoConfig,LLMConfig, andHistoricalConfig. Each of those requires env vars (e.g.SEAWEEDFS_S3_BUCKET,NOCODB_BASE_URL). The test helper_setup_env()was only called from the test runner'smain(), not from the test itself, so when the CI ran the e2e test directly via heredoc the env was empty andmain()returnedrc=2withconfig_error: SEAWEEDFS_S3_BUCKET is not set.Fix (commit
288cfcb). Added a one-line_setup_env()call inside the e2e test itself, so the test works under any runner (unittest,pytest, heredoc, CI, ad-hoc).All 7 fast historical tests also pass with this change. ci-pr run #740 is now queued against this new head.