For the Navigant Student Guidance workflow, I deployed n8n Community Edition inside a Docker container on Windows 11. This runtime connects to a live Notion student intake database and calls the Google Gemini API for reasoning and classification.
I started the integration on Gemini 2.5 Flash. Later, while hardening the system against concurrency failures, I moved the execution engine to gemini-3-flash-preview, which handled tool-calling and structured output more reliably under load.
I chose a self-hosted, containerized stack over Claude Desktop or a purely cloud-based automation tool for one practical reason: cost and control. Running n8n via Docker gave me zero execution costs and direct control over my own API tokens, instead of being limited by a volatile free-tier message cap.
My biggest success was moving past a simple linear automation and designing a goal-seeking agent that grounds its answers in real data instead of guessing.
Large language models will naturally hallucinate entry criteria and numerical thresholds when asked about university admissions requirements. To stop this, I built a custom JavaScript tool — uk_university_requirements_lookup — and wired it directly into the n8n agent node. This forced Gemini to make a tool call and cross-reference a verified data structure instead of inventing an answer, which eliminated hallucinated requirements during student profile processing.
The system did not work perfectly on the first real test, and I'm documenting the failure honestly rather than smoothing it over.
Failure 1 — Rate limiting: During a stress test with multi-row data pulled from the Notion API, the workflow triggered an HTTP 429 concurrency error from the Google AI Studio provider. Multiple overlapping sub-node tool calls fired at once and exceeded the provider's rate limit.
Failure 2 — Unmapped data: Separately, when the workflow processed a student record with an unmapped major (Psychology) that fell outside my existing data structure, the agent hit a hard boundary. The JavaScript logic had no path for the unexpected value and the run froze instead of failing gracefully.
gemini-3-flash-preview for more stable tool-calling behavior under load..toLowerCase().trim() normalizer so an unmapped major is caught and routed to a clean "Halted: Data Incomplete" status. Instead of freezing, the workflow now stops safely and flags the record for human review.That last decision is the one I'm most glad I made: when the agent doesn't know something, it should say so and stop — not guess. That's the standard I'm holding this system to as I keep building it.
The next upgrade is to replace the local JavaScript lookup dictionary with a persistent, relational PostgreSQL database container running in the same Docker network. That will let the Navigant Student Guidance Agent run proper multi-row, relational queries across a much larger set of active university criteria — instead of a hand-maintained lookup table.