I built a job-search tool for a family member — a full pipeline that discovers postings, scores them across 13 weighted dimensions, routes each into a pursuit strategy, generates tailored materials, and tracks every job through a human-approval gate. The whole thing runs on two third-party Python packages (requests and pyyaml), plain JSON/YAML/Markdown files on disk, and no database. Status: parked.
The architecture is worth writing about because it pushed file-based state further than I expected it to go, and because the things that broke were things I thought I had designed away.
Why files instead of a database
Early in the build, I piloted a third-party orchestration platform: running server, embedded database, org chart, issue-based task routing. One job went through it end to end successfully. Then I removed the entire orchestration layer, because the same capabilities were achieved with Python scripts and JSON files. The job-search pipeline itself kept working afterward, and applications did go out.
What stayed was fifteen named agent roles — onboarding, market research, collector, evaluator, strategist, resume architect, ATS reviewer, simulator, researcher, tracker, coach — each documented against a fixed Mission/Inputs/Outputs/Constraints/Approval-Rules/Tool-Access/Escalation template. The data layer is standard library and plain files. No server process.
How it works
Each job posting moves through a scoring pass (13 weighted dimensions with red-line violations and deal-breaker caps), then gets routed into one of five pursuit strategies by score band. The agent-facing API is pure re-exports: every function a conversational agent calls simply re-exports from one of the internal modules, so the agent sees a stable interface while the implementation stays flexible underneath.
Status is derived from artifacts present, not from any agent self-reporting. A job is tracked if its tracking file exists. A step is complete if its output file exists and its upstream approval gate has cleared. That principle — status derived from disk — is the pipeline's stated governance rule, and it caught its own violation.
What broke
An initialization function was marking a pipeline step "done" purely because the step's output file existed on disk, without checking whether the step's own upstream approval gate had actually cleared. 26 of 31 tracked jobs had a step incorrectly marked complete before approval had happened.
A related gap sat in the audit log. The log held exactly 1 entry for 31 jobs, because the state-initialization code never logged its own actions. Both were fixed in the same session: an explicit post-hoc dependency-enforcement pass, 6 new tests, and the log grew from 1 to 208 entries.
The fix was straightforward once I noticed the gap. What I had not designed away was the fact that "status is derived from artifacts" still requires you to derive the right status — and "file exists" is not the same as "gate cleared."
The numbers
Measured counts:
- 25,605 lines of Python source across 56 files.
- 445 test functions in the current suite; a dated snapshot recorded 147 passing.
- 26 of 31 tracked jobs found incorrectly marked complete before the dependency fix.
- 2 third-party Python dependencies; no database, no server process.
What is next
The tool is parked. It did its job — applications went out, interviews happened — and I am not building a generic job-search product. The architecture, though, is something I will reuse: file-based state scales further than I thought it would, and the agent-facing re-export layer kept the conversational interface stable through a dozen internal refactors.
The lesson I wrote down: if you derive status from what exists on disk, write a test that checks whether the thing you are deriving actually matches the rule you think you are enforcing. "File exists" is not always the right predicate.