CourseAI System Design: The Complete Course · Course guide · part 4 of 107
Part 4 · Course guide

The Project: Northdesk

19 min read·

Every lesson in this course is set at one company, on one project. Northdesk is made up. The company, its people, its tenants, every document, request, trace and number in the project are fictional and simulated. The real cases that open each lesson are separate, cited, and clearly marked as real.

The company

Northdesk is a software company in Manchester. It sells Northdesk Workplace, an AI helpdesk for employees, to other companies, which it calls tenants. A tenant's employees ask about leave, expenses, laptops and pay; they ask for things to be done; and the tenant's HR, IT and facilities teams get fewer tickets. Northdesk has 240 tenants and covers 410,000 employees (seats). It charges $8 a seat a month for Ask and $4 more for Cases, in US dollars, the same currency the model providers publish prices in.

The product

FeatureWhat it doesToday (October 2026)
AskAn employee asks a question; Ask answers from their own company's documents, with citations, using only documents that employee is allowed to read38,000 daily users, about 6.2 questions each
CasesA bounded agent resolves routine requests (a cracked laptop, a leave balance, an access request) by calling tools in the tenant's own systems. Some actions need a person's approval; some it must hand to a person9,000 runs a day
IntakeTenants connect drives and upload files; Intake parses, validates, redacts personal data, chunks and indexes them in the background1,100,000 documents across all tenants, about 6,000 tokens each
DigestA weekly summary of unanswered questions for each tenant's HR team, built in a batchweekly

The problem

Northdesk's first version was built fast, by a small team, for small tenants. Then larger tenants signed. On 14 September 2026 Tallis Retail Group switched Ask on for all 54,000 of its staff, and traffic in the sample doubled in two weeks. On 22 September the main model provider slowed down for 40 minutes and Ask timed out for some users. On 29 September a prompt change made answers longer; nobody connected it to the cost rise until Ana asked why the bill had jumped.

Joel, the head of product, has a target for the second quarter of 2027: 100,000 daily Ask users, 10 questions each, 2,000 input and 500 output tokens per question, a 3-second response target, and 60,000 Cases runs a day. Helen, the CTO, wants a design for the next version of the platform that meets it, with the evidence, before anyone writes production code.

You are the AI platform engineer Northdesk hired to produce that design and prove each decision. The target is the roadmap's worked estimate, so you will find out in Module 3 that the 3-second target cannot be met as written.

What this course uses the project for

This course teaches AI system design: what to put where, what to leave out, and which tradeoffs you are choosing deliberately. Northdesk is the instrument. Each module adds one decision and its proof to one repository, so by the capstone you can defend the whole design in a review, with every number regenerated from code.

It is not BrightCart (AI Engineering), Harbourline Freight (Forward Deployed Engineer), the returns desk (Agentic AI Engineer), the Quillpath support desk (Large Language Models), Hollin Answers (RAG), the notes assistant (MCP), the TinyFormer lab (Transformers), Booknook (Python) or the Tidewell Cycles inbox (NLP). Northdesk is a different company, with a different cast, data and problem.

The people

NameRoleIn the project
Helen AchterbergChief technology officerSigns off architecture decision records. Wants the simplest design that meets the bar, and decisions that can be reversed
Joel MarchettiHead of productOwns the requirements: who uses Northdesk, what jobs it does, how success is measured, the Q2 2027 target. Pushes for Cases to do more
Funmi OyelaranPlatform and reliability leadOn call. Owns capacity, deployment and the GPU question; ran the 22 September incident
Ana KowalczykFinance leadCost per request, per tenant and per seat; margin at $8 a seat; when self-hosting would pay
Leila RahimiSecurity and compliance leadTrust boundaries, tenant isolation, data residency, provider terms, audit. Her questions are answered from named, verified sources and marked "not legal advice"
Gareth FenwickStaff engineer and design reviewerRuns the design reviews. Asks the questions interviewers and reviewers ask: why not a workflow, what does it cost, how would you know it got worse, where does untrusted text get in, what happens when the provider fails, should this be built at all
YouAI platform engineerDesign the platform and prove each decision

Tenant staff appear only as generated users in the shared data. Do not add named Northdesk staff; a tenant user may appear in one lesson only. The exceptions below were written before the consistency pass (23 October 2026) and are kept rather than renamed, because their lessons' code and the labs that copy it use them; they are part of the cast from now on. Golden-set requesters cited only by user id (for example t-tallis-u035) may recur.

NameUser idWhoAppears in
Vera Parkt-tallis-u004Tallis Retail Group, operationsModule 1 Lesson 3 (her leave balance through Cases), Module 10 Lesson 1 (her Ask leave question)
Maya Ortizt-tallis-u029Tallis Retail Group, operationsModule 9 Lessons 1 and 6 and the Module 9 and 10 labs (golden task C012, a second monitor)
Sven Rossit-tallis-u054Tallis Retail Group, Maya Ortiz's managerModule 9 Lesson 6 and the Module 9 and 10 labs (approves C012)

The tenants in the data

Six of the 240 tenants have generated data. They were chosen to differ in every way that changes a design.

IdTenantCountryData must stay inSeatsIsolation tierFeaturesWhy it is in the sample
t-fairhavenFairhaven HousingUKuk2,400sharedAskA small tenant
t-brennanBrennan & Kloss LogisticsGermanyeu9,800sharedAsk, CasesEU residency
t-halcyonHalcyon Health PartnersUSus31,000siloedAskIts contract requires its own index and no provider data retention
t-tallisTallis Retail GroupUKuk54,000sharedAsk, CasesThe largest tenant: the noisy neighbour and the 14 September jump
t-orielOriel SoftwareUSus1,200sharedAsk, CasesSmall but uses Cases heavily
t-marlowMarlow County CouncilUKuk6,500hybridAskPublic sector; shared compute, its own index

Each tenant's documents state different policy values (leave days, meal limits, pay day, salary bands), so an answer that quotes another tenant's value is a visible cross-tenant leak.

The rules in the documents

Every tenant has the same ten policy documents with its own values. These are what the golden questions ask about:

DocumentWho may read itWhat it states
Annual leave policy (2025)all staffthe old number of leave days; superseded
Annual leave policy (2026)all staffthe current number of leave days
Travel and expenses policyall staffthe daily meal limit
Hybrid working guidelinesall staffremote days a week
Laptop and device standardall stafflaptop refresh cycle and helpdesk hours
Family leave policyall staffweeks of parental leave on full pay
Payroll calendar and payslipsall staffpay day
Salary bands 2026 (confidential)hr-confidential, execsthe band C salary range
Executive meeting minutesexecsrestructuring, a hiring freeze, the office move
IT incident runbookitwhat to do when the identity provider is down

The values are in FACTS in 06-shared-data.md and never vary.

The Cases tool surface

ToolWhat it doesWrites?Needs approval?
hris.get_employeeLook up the requester's employee recordnono
hris.get_leave_balanceRead the requester's remaining leave daysnono
assets.get_deviceRead the devices assigned to the requesternono
assets.request_itemOrder equipment against the team budgetyesyes
tickets.createOpen a helpdesk ticketyesno
tickets.listList the requester's open ticketsnono
tickets.closeClose ticketsyesyes
access.request_grantGrant access to an applicationyesyes

Some requests must never be handled by the agent at all: a request to change bank details is escalated to a person with no tool called. The tools run as MCP servers in Module 9; in production each would sit in front of the tenant's real HR, asset and ticketing systems.

Dates

  • The shared data covers Monday 7 September to Sunday 4 October 2026 (28 days). The snapshot was taken on Monday 5 October 2026 (DATA_AS_OF). Code counts days to DATA_AS_OF, never to date.today(), so lessons give the same answers whenever you run them.
  • Traffic and trace times are in UTC; tenants' working hours follow their own time zones, which is why the busiest hour is when the UK afternoon overlaps the US morning.
  • Dates in prose are written day first ("22 September 2026"); dates in data are ISO (2026-09-22).

Key events in the data

WhenWhat happenedWhere the course uses it
Monday 14 September 2026Tallis Retail Group switches Ask on for all stores (from 10% of seats to all, over three days)Module 3 (traffic and growth), Module 14 (noisy neighbours)
Tuesday 22 September 2026, 13:00 to 13:40 UTCThe primary model provider slows down; 30% of its calls hit the gateway's 8-second timeout and fall back to the secondary providerModule 7 (fallback), Module 11 (the replay, circuit breakers, timeouts), the Module 11 lab
Tuesday 29 September 2026, 10:00 UTCThe Ask prompt changes from v7 to v8: a longer system prompt, 12 chunks instead of 8, longer answersModule 13 (versioning everything together), Module 15 Lesson 5 (the debugging case)

The two providers in the data are called primary and secondary on purpose. The incident is simulated and is not about any real company. In the lessons, primary is whichever provider you set in PROVIDER and secondary is the other.

The project weeks

WeekMondayModuleWhat happensDesign step
12 November 20261Why Northdesk's first version behaves unlike any system the team built before; the failure-mode table startsorientation
29 November2Joel's requirements, written down with error rates and refusal rules1, 2
316 November3The arithmetic: the 3-second target is impossible as written; the estimator and the numbers sheet3
423 November4The rung of the ladder for Ask and for Cases; ADR-0014
530 November5The data path: ingestion, storage, redaction, lineage5
67 December6Permissioned retrieval, scored for leaks5
714 December7The model gateway: routing, cascade, fallback, budgets6
821 December8The Ask workflow, sessions, memory and the job queue6
928 December9The bounded Cases agent over MCP, with approvals and an audit trail6, 7
104 January 202710The service, caching, deployment shapes, isolation, CI6
1111 January11The 22 September incident replayed and designed out9
1218 January12Trust boundaries mapped and attacked7
1325 January13Eval gates and tracing8
141 February14Ten times the traffic; an embedding migration10
158 February15Rehearsing the design review; practice designsall
1615 February16Capstone: the design review with Helen and Garethall

The "design step" column refers to the roadmap's ten-step order in 01-how-this-course-works.md. Gemini 3.8 Flash's price doubles on 1 January 2027, in week 9: the price table in course-guide/02 says so, and the cost lessons use it as the example of a dated price.

Fixed names

So that modules fit together, these names do not change:

  • The repository is northdesk-platform/; the package is northdesk/; tests live in tests/; evaluation in evals/; the web service in service/; design documents in docs/.
  • northdesk/data.py holds blocks 1 to 6 of the shared data, copied exactly.
  • northdesk/llm.py holds the setup cell from course-guide/02 and ask(model, messages), created once (Module 4 lab).
  • Every component that calls a model goes through the model port, ModelPort.complete(messages, tier) -> Completion in northdesk/ports.py (Module 4), so the gateway (Module 7) and the scripted stand-ins used in tests are interchangeable.
  • The gateway's public call is Gateway.complete(request) -> Completion, where the request carries tenant_id, feature, tier and budget_key, and the completion carries the text, the provider used, token counts, cost in US dollars and latency.
  • Retrieval returns list[Hit] (document id, chunk id, score, text), already filtered by the user's groups and tenant: nothing downstream sees a chunk the user may not read.
  • Every Ask answer is an Answer (text, cited document ids, status answered, not_found or provider_error).
  • Every span from Module 9 on has trace_id, span_id, parent_id, name, start_ms, duration_ms and attrs, the same shape as TRACES.
  • docs/numbers.md is generated by northdesk/estimate.py and never edited by hand.

One repository: which module adds which file

In Colab each lesson writes the files it needs with %%writefile and imports them; on a laptop the same files form the repository. Tests run with !python -m pytest -q in Colab and pytest -q locally. Every lab from Module 2 on starts by checking that the earlier modules' files are present and writing back any that are missing (the %%ensure cells), so you can start at any module.

The table lists every file a lesson or lab writes, generated on 23 October 2026 from the %%writefile and %%ensure cells in the published lessons and labs (each file under the module that first writes it; package __init__.py files are left out; "(generated)" marks a file written by code rather than by a cell). Lessons write their own small component files; the lab then writes the module's consolidated files, such as estimate.py, retrieve.py, model_registry.py or reliability.py. Module 7 Lesson 1's draft docs/adr/ADR-002-model-per-task.md is a draft number: ADR-002 is Module 5's data path, and the Module 7 lab records the model decision as ADR-004.

ModuleLessons addThe lab adds
1northdesk/basics/queue_sim.py, northdesk/basics/replicas.py, northdesk/basics/retry.py, northdesk/probe.py, northdesk/trace.py.gitignore, docs/design.md, docs/failure-modes.md, northdesk/data.py, requirements.txt, tests/test_data.py, tests/test_trace.py
2docs/problem-frame.md (generated), evals/grade.py, evals/quality_gate.py, northdesk/design_check.py, northdesk/requirements.py, northdesk/residency.py, northdesk/rules.pydocs/requirements.md, tests/test_requirements.py
3northdesk/budget.py, northdesk/ceiling.py, northdesk/cost.py, northdesk/cost_business.py, northdesk/cost_paths.py, northdesk/cost_traces.py, northdesk/data_ops.py, northdesk/latency.py, northdesk/latency_budget.py, northdesk/percentiles.py, northdesk/prompt_layout.py, northdesk/sizing_gpu.py, northdesk/sizing_vectors.py, northdesk/stream_timing.py, northdesk/tiering.py, northdesk/token_volume.py, northdesk/tokens.py, northdesk/trace_hops.py, northdesk/traffic.py, northdesk/traffic_measure.py, tests/test_cost.py, tests/test_cost_choices.py, tests/test_latency.py, tests/test_traffic.pydocs/adr/ADR-000-ask-streams.md, docs/numbers.md (generated), northdesk/estimate.py, tests/test_estimate.py
4northdesk/ask_flow.py, northdesk/boundaries.py, northdesk/cases_router.py, northdesk/choose.py, northdesk/classify.py, northdesk/contracts.py, northdesk/intents.py, northdesk/ports.py, northdesk/prompts.py, northdesk/route_llm.py, northdesk/rungs/high.py, northdesk/rungs/low.py, northdesk/rungs/score.py, northdesk/rungs/search.py, northdesk/rungs/shapes.py, northdesk/rungs/stand_in.pydocs/adr/ADR-001-architecture-rung.md, northdesk/ladder.py, northdesk/llm.py, tests/test_ports.py
5northdesk/acl.py, northdesk/backfill.py, northdesk/blobs.py, northdesk/catalog.py, northdesk/deadletter.py, northdesk/erase.py, northdesk/freshness.py, northdesk/ingest.py, northdesk/lineage.py, northdesk/parse_html.py, northdesk/redact.py, northdesk/search.py, northdesk/store.py, northdesk/validate.py, northdesk/vectors.pydocs/adr/ADR-002-data-path.md, northdesk/datapath.py, northdesk/governance.py, northdesk/intake.py, northdesk/sources.py, tests/test_ingest.py
6evals/retrieval_split.py, northdesk/agentic.py, northdesk/chunking.py, northdesk/embed_index.py, northdesk/federate.py, northdesk/hierarchy.py, northdesk/hybrid.py, northdesk/query_plan.py, northdesk/rerank.py, northdesk/retrieval_prod/answer.py, northdesk/retrieval_prod/chunks.py, northdesk/retrieval_prod/index.py, northdesk/retrieval_prod/refresh.py, northdesk/structured.pydocs/adr/ADR-003-retrieval-pipeline.md, evals/retrieval_eval.py, northdesk/answer.py, northdesk/retrieve.py, tests/test_retrieve.py
7docs/adr/ADR-002-model-per-task.md (generated), northdesk/backends.py, northdesk/batching.py, northdesk/cascade.py, northdesk/coupling.py, northdesk/degraded.py, northdesk/fallback.py, northdesk/fingerprint.py, northdesk/gateway.py, northdesk/inference_timing.py, northdesk/lifecycle.py, northdesk/model_candidates.py, northdesk/model_contract.py, northdesk/model_fakes.py, northdesk/model_options.py, northdesk/model_select.py, northdesk/model_tasks.py, northdesk/reasoning_cost.py, northdesk/router.py, northdesk/serving_capacity.py, northdesk/shadow.py, northdesk/upgrade.py, tests/test_model_contract.pydocs/adr/ADR-004-model-layer.md, northdesk/model_registry.py, northdesk/requestpath.py, tests/test_gateway.py
8docs/ask-states.md (generated), northdesk/ask_machine.py, northdesk/ask_steps.py, northdesk/checkpoint.py, northdesk/compaction.py, northdesk/context_budget.py, northdesk/idempotency.py, northdesk/jobs.py, northdesk/memory.py, northdesk/memory_policy.py, northdesk/memory_scope.py, northdesk/memory_sync.py, northdesk/progress.py, northdesk/session.py, northdesk/session_layout.py, northdesk/sync_graph.py, northdesk/workflow.pydocs/adr/ADR-005-background-jobs.md, northdesk/ask_ports.py, northdesk/ask_turn.py, northdesk/digest.py, tests/test_jobs.py, tests/test_workflow.py
9evals/trajectory_eval.py, northdesk/action_guard.py, northdesk/agent/autonomy.py, northdesk/agent/ceilings.py, northdesk/agent/graph.py, northdesk/agent/loop.py, northdesk/agent/nodes.py, northdesk/agent/systems.py, northdesk/agent/verify.py, northdesk/audit.py, northdesk/cases_job.py, northdesk/cases_tools.py, northdesk/exec_policy.py, northdesk/killswitch.py, northdesk/multi/delegation.py, northdesk/multi/guards.py, northdesk/multi/ledger.py, northdesk/multi/orchestrator.py, northdesk/multi/procurement_a2a.py, northdesk/multi/runner.py, northdesk/multi/tools.py, northdesk/plan_apply.py, northdesk/sandbox.py, northdesk/spans.py, northdesk/toollayer/compat.py, northdesk/toollayer/context_cost.py, northdesk/toollayer/gateway.py, northdesk/toollayer/registry.py, northdesk/toollayer/servers.py, northdesk/toollayer/specs.py, northdesk/tools/adapter.py, northdesk/tools/cases_server.py, northdesk/tools/endpoints.py, northdesk/tools/handlers.py, northdesk/tools/intents.py, northdesk/tools/results.py, northdesk/tools/systems.pydocs/adr/ADR-006-cases-agent-bounds.md, docs/adr/ADR-007-cases-tool-surface.md, docs/adr/ADR-008-tool-gateway.md, docs/adr/ADR-009-cases-one-agent.md, docs/adr/ADR-010-cases-execution-environment.md, docs/adr/ADR-011-cases-runs-as-jobs.md, northdesk/agent/cases_agent.py, northdesk/agent/cases_worker.py, northdesk/agent/scripted.py, northdesk/tools/access_server.py, northdesk/tools/assets_server.py, northdesk/tools/cases_gateway.py, northdesk/tools/hris_server.py, northdesk/tools/serve.py, northdesk/tools/tickets_server.py, tests/test_agent.py
10evals/ask_golden.json (generated), northdesk/admission.py, northdesk/ask_deps.py, northdesk/cache.py, northdesk/cloud_adapters.py, northdesk/egress.py, northdesk/front_door.py, northdesk/model_regions.py, northdesk/pipeline/bundle.py, northdesk/pipeline/canary.py, northdesk/pipeline/parity.py, northdesk/pipeline/record_sim.py, northdesk/pipeline/replay.py, northdesk/placement.py, northdesk/platform_limits.py, northdesk/region_check.py, northdesk/scheduling/autoscale.py, northdesk/scheduling/coldstart.py, northdesk/scheduling/health_app.py, northdesk/scheduling/images.py, northdesk/scheduling/load.py, northdesk/scheduling/probes.py, northdesk/secret_loader.py, northdesk/storage_rules.py, northdesk/stream_model.py, northdesk/worker_tiers.py, northdesk/zones.py, service/request_tier.py, service/sse.py, service/stream_app.py, tests/test_portability.py, tests/test_release_gate.py.dockerignore, .github/workflows/ci.yml, Dockerfile, deploy/gpu/model-server.yaml, docker-compose.yml, docs/adr/ADR-012-cache-layers.md, docs/adr/ADR-013-deployment-shapes.md, docs/adr/ADR-014-containers-and-gpu-scheduling.md, docs/adr/ADR-015-cloud-and-lock-in.md, docs/adr/ADR-016-zones-egress-cells.md, docs/adr/ADR-017-release-pipeline.md, lifecycle.json, northdesk/config.py, northdesk/rollout.py, service/app.py, service/ask_route.py, service/early_exit.py, service/slot.py, service/work_routes.py, service/worker.py, tests/test_service.py
11northdesk/breaker.py, northdesk/degrade_ladder.py, northdesk/failure_catalog.py, northdesk/faq_fallback.py, northdesk/fault_replay.py, northdesk/guarded.py, northdesk/ratelimit.py, northdesk/retry_budget.py, northdesk/timeouts.pydocs/adr/ADR-018-failure-design.md, docs/adr/ADR-019-reliability-mechanisms.md, northdesk/bulkhead_sim.py, northdesk/failure_policy.py, northdesk/reliability.py, northdesk/replay_0922.py, tests/test_faults.py
12docs/trust-boundaries.md (generated), northdesk/trust/abac.py, northdesk/trust/credentials.py, northdesk/trust/layers.py, northdesk/trust/output_check.py, northdesk/trust/replay.py, northdesk/trust/scope.py, northdesk/trustmap/flows.py, northdesk/trustmap/parts.py, northdesk/trustmap/permits.py, northdesk/trustmap/tenancy.py, northdesk/trustmap/trifecta.pydocs/adr/ADR-020-two-controls-per-harm-path.md, docs/adr/ADR-021-one-policy-and-checked-answers.md, evals/control_checks.py, northdesk/policy.py, northdesk/validate_output.py, service/trusted.py, tests/test_policy.py
13evals/components.py, evals/from_production.jsonl (generated), evals/from_production.py, evals/gates.py, evals/scripted_ask.py, northdesk/experiment.py, northdesk/observe/attribution.py, northdesk/observe/drift.py, northdesk/observe/feedback.py, northdesk/observe/genai.py, northdesk/observe/registry.py, northdesk/observe/scorecard.py, northdesk/shadow_bundle.pydocs/adr/ADR-022-gates-per-component-shadow-then-ab.md, docs/adr/ADR-023-spans-drift-and-the-failure-loop.md, evals/golden_from_production.json (generated), evals/harness.py, northdesk/drift.py, northdesk/feedback.py, northdesk/otel.py, northdesk/registry.py, releases/<bundle>.json (generated), releases/log.jsonl (generated), reports/quality.md (generated), tests/test_harness.py
14northdesk/evolve/compare.py, northdesk/evolve/dual.py, northdesk/evolve/lockin.py, northdesk/evolve/retire.py, northdesk/evolve/vindex.py, northdesk/scale/ceilings.py, northdesk/scale/deferral.py, northdesk/scale/quota.py, northdesk/scale/replay.pydocs/adr/ADR-024-scaling-ceilings.md, docs/adr/ADR-025-embedding-migration.md, docs/adr/ADR-026-lock-in-a-year-ahead.md, docs/migrations.md (generated), northdesk/exits.py, northdesk/migrate.py, northdesk/scale_sim.py, tests/test_migrate.py, tests/test_scale.py
15docs/adr/ADR-NNN-cases-one-agent.md (generated), docs/adr/ADR-NNN-release-pipeline.md (generated), docs/design-review.md (generated), docs/practice/ (generated practice ADRs), docs/review-evidence.md (generated), northdesk/debugging/bill_split.py, northdesk/debugging/critical_path.py, northdesk/debugging/version_diff.py, northdesk/debugging/what_if.py, northdesk/drills/sheet.py, northdesk/evidence/claims.py, northdesk/evidence/finder.py, northdesk/evidence/harvest.py, northdesk/evidence/records_adr000.py, northdesk/evidence/records_adr001.py, northdesk/evidence/records_more.py, northdesk/practice_adr.py, northdesk/practice_adrs.py, northdesk/practice_designs.py, northdesk/practice_estimate.py, northdesk/review/adr.py, northdesk/review/aloud.py, northdesk/review/assemble.py, northdesk/review/clarify.py, northdesk/review/failures.py, northdesk/review/narrate.py, tests/test_practice.pydocs/debugging-case.md (generated), northdesk/debugging/by_bundle.py, northdesk/debugging/case.py, northdesk/debugging/case_doc.py, northdesk/drills/further.py, northdesk/drills/further_adrs.py, northdesk/review/adr_review.py, northdesk/review/practice.py, northdesk/review/repo_claims.py, northdesk/review/repo_review.py, northdesk/review/said.py, northdesk/review/settle.py, tests/test_debugging.py, tests/test_review.py
1615 February(the capstone)

The design artifacts

Design documents are part of the deliverable, not decoration. Each is written from measured numbers.

ArtifactWhat it holdsRule
docs/design.md, then DESIGN.mdThe design in the roadmap's ten-step orderEvery number in it comes from docs/numbers.md or a measured run
docs/numbers.mdTraffic, tokens, storage, GPU memory, cost per request, per tenant and per month, the latency budget per hopGenerated by northdesk/estimate.py; never edited by hand
docs/adr/ADR-NNN-title.mdOne decision eachUses the template below
Data path and request path figuresWhere content comes from and how it stays fresh; every hop from question to answer, annotated with latency and costDrawn as course figures (the same <figure> blocks as the lessons), copied into docs/design.md and annotated with the learner's own measured numbers
docs/trust-boundaries.mdEvery untrusted input, what it can reach, the control on each pathChecked by evals/control_checks.py
docs/failure-modes.mdComponent, failure, how it is detected, fail-open or fail-closed, fallback, blast radiusEach row has a test in tests/test_faults.py or says why not
docs/migrations.mdHow to change the embedding model, the vector store and the provider without downtimeCosted with the estimator

The ADR template, used from Module 4 on:

text
# ADR-NNN: <decision in a few words>

Status: proposed | accepted | superseded by ADR-MMM
Date: <ISO date>
Deciders: <names from the cast>

## Context
<the problem, with the numbers from docs/numbers.md that force a decision>

## Options considered
<two or three, each with cost, latency, quality and what it locks in>

## Decision
<the choice, in one or two sentences>

## Consequences
<what gets easier, what gets harder, what we now have to operate>

## Revisit when
<the measurable condition that would change the decision, for example "Ask traffic passes 200 RPS at peak">

The finished platform

  • A requirements document with an explicit error rate, cost asymmetry and refusal rules, as data the gates read
  • A numbers sheet generated by an estimator that reproduces the roadmap's worked estimate and then Northdesk's own
  • An ingestion pipeline that is incremental, idempotent, redacts personal data and keeps lineage
  • Permissioned, tenant-isolated retrieval, scored for recall and for leaks
  • A model gateway with routing, a cascade, a fallback chain, caching, budgets, a circuit breaker and rate limits, tested with fault injection
  • A LangGraph Ask workflow with session state and compaction, and a job queue for the background paths
  • A bounded Cases agent over MCP, with ceilings, approvals, a sandbox and an audit trail
  • A FastAPI service tested with TestClient, a Docker Compose stack and a CI pipeline with eval gates
  • An eval harness with gates, and traces in OpenTelemetry
  • ADRs, data and request path figures, a trust boundary map, a failure-mode table and a migration plan

The one idea

Choose the boring version until the numbers say otherwise. Most proposed AI architectures are more autonomous, more multi-agent and more fine-tuned than the problem needs. Every component in this course has to earn its place with a number: a cost it saves, a latency it removes, a failure it contains, a leak it prevents.

Facts settled in lessons (18 October 2026, updated 23 October 2026)

Later lessons and labs use these values; they were added in the lessons named and are labelled there. Where a lesson's code still follows an earlier reading, its prose says so and the module lab applies the value below.

  • Model spend may be at most 10% of seat revenue: with the $8 Ask seat and about 220 requests per seat a month, the cost ceiling is $0.003636 per Ask request (8 x 10% / 220, written to six decimal places; earlier text rounds it to $0.0036) (Module 2 Lesson 3).
  • 22 working days a month; Tallis's staff-chat webhook waits 30 seconds; the team has no GPU operations experience (Module 2 Lesson 3).
  • Residency, read strictly from provider pages checked 18 October 2026 (residency.py, Module 2 Lesson 3; this code wins where prose differs): the Gemini Developer API (gemini-paid) cannot be shown to keep processing in any named region, so it fails residency for every tenant, US included; Groq is "ask" and passes only with data retention switched off, pending Leila's check; managed cloud regions are "ask" in residency.py and are judged per model and region by northdesk/region_check.py (Module 10 Lesson 4). Designs that send a tenant's data to a model must route it to a provider or region that passes, or say why not (Module 2 Lesson 3).
  • Illustrative error prices: $4 unanswered, $40 wrong, $2,000 unsafe; human review capacity 400 answers a day (Module 2 Lesson 2).
  • Caching on Gemini: Google's caching page (checked 18 October 2026) gives a 4,096-token minimum for Gemini 3.8 Flash, so an Ask prompt of about 2,000 tokens cannot be cached there; guide 06's cached Ask traces only make sense on Groq. Gemini's cached input price also doubles on 1 January 2027 ($0.075 to $0.15 per million), and cache storage costs $0.50 per million tokens per hour (Module 3 Lesson 4).
  • Self-hosting break-even uses Module 3 Lesson 3's sizing (2.65 GPUs of work, 4 GPUs, $5,840 a month) and illustrative people costs of $3,000 or $10,000 a month (Module 3 Lesson 4).
  • First-token target: Ask's time to first visible token is at most 1,500 ms at p95 (proposed in Module 3 Lesson 5; Joel's 3-second target becomes this once answers stream).
  • Freshness targets: policy documents searchable within 1 hour of a change, everyday documents within 24 hours, both at p95; uploads limited to 50 MB; Marlow edits its leave policy on 5 October (carry-over five to eight days, no FACTS value changes) (Module 5 Lesson 1).
  • Retention proposal (not legal advice): answer cache 1 day, backups 29 days, traces 30 days (Module 5 Lesson 2). MinIO's repository was archived on 25 April 2026; the course's self-hosted object store example is SeaweedFS (Apache 2.0). Qdrant local mode ignores payload indexes.
  • 22 September fallback, corrected 19 October 2026: because the Gemini Developer API fails residency for every tenant (residency.py), no tenant may fall back to it. Fallback goes to Groq (retention off, "ask" pending Leila) or to degraded mode. Module 7 Lesson 2's replay, which let US tenants fall back to Gemini, and guide 06's traces, which record 27 fallbacks to the secondary including 16 for UK and EU tenants, show the first design breaking this rule; later lessons teach that as the mistake the rule fixes.
  • Bedrock residency (Module 7 Lesson 3, checked 18 October 2026): Bedrock's gpt-oss-20b model card lists In-Region availability in eu-west-2 (London), which keeps requests within one region, so it can pass UK residency. Google's managed gpt-oss-20b (gpt-oss-20b-maas) is us-central1 only and is to be retired on 21 October 2026.
  • Retrieval (Module 6 Lesson 1): retrieval budget 280 ms (from Module 3 Lesson 5), measured 69.8 ms for the whole pipeline on one CPU; chunks are paragraphs capped at 180 words with the document title in front.
  • Memory retention proposal (not legal advice): open case 30 days from last use, preference 180 days, reviewed team and organisation notes 365 days; memory adds a 150-token budget per request (Module 8 Lesson 3).
  • Sessions (Module 8 Lesson 2): context budget 4,000 tokens per Ask request (latency bound 5,394), history allowance 1,105 tokens, 30-minute idle timeout (design assumption).
  • Cloud Run GPUs (Module 10 Lesson 3, checked 18 October 2026): the GPU page lists no London region; L4 GPUs are in Belgium, the Netherlands, Iowa, Northern Virginia, Singapore and Mumbai (invitation only). One GPU serves about 17.5 RPS (from Module 3 Lesson 3).
  • Groq retention (Module 10 Lesson 5): with data retention on, Groq stores data in the US and fails UK and EU residency, so Northdesk runs Groq with retention switched off. HashiCorp Vault is Business Source License from 1.15.0; OpenBao (MPL-2.0) is the open-source option.
  • Release bundle names (Module 10 Lesson 6): the 29 September change is versioned as prompt ask-v8 plus config cfg-12 (12 chunks) on an unchanged model; v7's bundle is prompt ask-v7 with cfg-8. A canary would have rolled it back at 10:50 after 205 requests (output tokens +40%); with no canary 221,334 requests got v8.
  • Residency by cloud (Module 10 Lesson 4, pages checked 18 October 2026; not legal advice): the managed clouds now state where processing happens, so a model-and-region pair can pass. For UK tenants each cloud has one passing option: gemini-3.5-flash on Gemini Enterprise Agent Platform in europe-west2 (the Gemini Developer API still fails), gpt-oss-20b on Bedrock In-Region in eu-west-2, and gpt-4.1-mini on Foundry Standard in uksouth. Google's quickstart 'global' location fails. Bedrock EU Geo routing and Azure EU Data Zone stay 'ask' for EU tenants. residency.py still says 'ask' for managed regions; northdesk/region_check.py (place_ok over model_regions.py) gives the per-pair answer.
  • Semantic cache (Module 10 Lesson 1): threshold 0.95 per tenant scope (local MiniLM put different questions above true paraphrases); cache keys always include the tenant.
  • Practice-design ADRs (Module 15 Lessons 3 and 4) go in docs/practice/, not docs/adr/: they are exercises, not Northdesk decisions, and stay out of the decision register.
  • Two numbers settled by the Module 15 lab (northdesk/review/settle.py): the cost ceiling is $0.003636 everywhere, and CONSTRAINTS["latency"] in northdesk/requirements.py says first token within 1.5 s at p95, replacing Module 2's 3.0 s, to match ADR-000.
  • Planted notes (Module 5 lab, Module 12 lab): Module 5's Intake quarantines guide 06's planted notes, so on the retrieval path they never reach a model. Module 12's lessons treat the pre-filter on its own, where the notes pass; the Module 12 lab found that the Module 10 service built Ask's candidates from raw documents, which let a planted note into context for 6 of 54 golden questions, and service/trusted.py (harden()) closes that path.
  • Cases tool names: guide 03's eight tools are the per-intent surface (Module 9 Lesson 2). The MCP name is hris.get_employee; direct function-calling definitions use hris_get_employee (dots become underscores, Module 9 Lesson 2), and Module 9 Lesson 3's tool gateway uses <server>__<tool>, hris__get_employee.