The Project: Northdesk
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
| Feature | What it does | Today (October 2026) |
|---|---|---|
| Ask | An employee asks a question; Ask answers from their own company's documents, with citations, using only documents that employee is allowed to read | 38,000 daily users, about 6.2 questions each |
| Cases | A 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 person | 9,000 runs a day |
| Intake | Tenants connect drives and upload files; Intake parses, validates, redacts personal data, chunks and indexes them in the background | 1,100,000 documents across all tenants, about 6,000 tokens each |
| Digest | A weekly summary of unanswered questions for each tenant's HR team, built in a batch | weekly |
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
| Name | Role | In the project |
|---|---|---|
| Helen Achterberg | Chief technology officer | Signs off architecture decision records. Wants the simplest design that meets the bar, and decisions that can be reversed |
| Joel Marchetti | Head of product | Owns the requirements: who uses Northdesk, what jobs it does, how success is measured, the Q2 2027 target. Pushes for Cases to do more |
| Funmi Oyelaran | Platform and reliability lead | On call. Owns capacity, deployment and the GPU question; ran the 22 September incident |
| Ana Kowalczyk | Finance lead | Cost per request, per tenant and per seat; margin at $8 a seat; when self-hosting would pay |
| Leila Rahimi | Security and compliance lead | Trust boundaries, tenant isolation, data residency, provider terms, audit. Her questions are answered from named, verified sources and marked "not legal advice" |
| Gareth Fenwick | Staff engineer and design reviewer | Runs 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 |
| You | AI platform engineer | Design 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.
| Name | User id | Who | Appears in |
|---|---|---|---|
| Vera Park | t-tallis-u004 | Tallis Retail Group, operations | Module 1 Lesson 3 (her leave balance through Cases), Module 10 Lesson 1 (her Ask leave question) |
| Maya Ortiz | t-tallis-u029 | Tallis Retail Group, operations | Module 9 Lessons 1 and 6 and the Module 9 and 10 labs (golden task C012, a second monitor) |
| Sven Rossi | t-tallis-u054 | Tallis Retail Group, Maya Ortiz's manager | Module 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.
| Id | Tenant | Country | Data must stay in | Seats | Isolation tier | Features | Why it is in the sample |
|---|---|---|---|---|---|---|---|
t-fairhaven | Fairhaven Housing | UK | uk | 2,400 | shared | Ask | A small tenant |
t-brennan | Brennan & Kloss Logistics | Germany | eu | 9,800 | shared | Ask, Cases | EU residency |
t-halcyon | Halcyon Health Partners | US | us | 31,000 | siloed | Ask | Its contract requires its own index and no provider data retention |
t-tallis | Tallis Retail Group | UK | uk | 54,000 | shared | Ask, Cases | The largest tenant: the noisy neighbour and the 14 September jump |
t-oriel | Oriel Software | US | us | 1,200 | shared | Ask, Cases | Small but uses Cases heavily |
t-marlow | Marlow County Council | UK | uk | 6,500 | hybrid | Ask | Public 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:
| Document | Who may read it | What it states |
|---|---|---|
| Annual leave policy (2025) | all staff | the old number of leave days; superseded |
| Annual leave policy (2026) | all staff | the current number of leave days |
| Travel and expenses policy | all staff | the daily meal limit |
| Hybrid working guidelines | all staff | remote days a week |
| Laptop and device standard | all staff | laptop refresh cycle and helpdesk hours |
| Family leave policy | all staff | weeks of parental leave on full pay |
| Payroll calendar and payslips | all staff | pay day |
| Salary bands 2026 (confidential) | hr-confidential, execs | the band C salary range |
| Executive meeting minutes | execs | restructuring, a hiring freeze, the office move |
| IT incident runbook | it | what 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
| Tool | What it does | Writes? | Needs approval? |
|---|---|---|---|
hris.get_employee | Look up the requester's employee record | no | no |
hris.get_leave_balance | Read the requester's remaining leave days | no | no |
assets.get_device | Read the devices assigned to the requester | no | no |
assets.request_item | Order equipment against the team budget | yes | yes |
tickets.create | Open a helpdesk ticket | yes | no |
tickets.list | List the requester's open tickets | no | no |
tickets.close | Close tickets | yes | yes |
access.request_grant | Grant access to an application | yes | yes |
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 toDATA_AS_OF, never todate.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
| When | What happened | Where the course uses it |
|---|---|---|
| Monday 14 September 2026 | Tallis 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 UTC | The primary model provider slows down; 30% of its calls hit the gateway's 8-second timeout and fall back to the secondary provider | Module 7 (fallback), Module 11 (the replay, circuit breakers, timeouts), the Module 11 lab |
| Tuesday 29 September 2026, 10:00 UTC | The Ask prompt changes from v7 to v8: a longer system prompt, 12 chunks instead of 8, longer answers | Module 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
| Week | Monday | Module | What happens | Design step |
|---|---|---|---|---|
| 1 | 2 November 2026 | 1 | Why Northdesk's first version behaves unlike any system the team built before; the failure-mode table starts | orientation |
| 2 | 9 November | 2 | Joel's requirements, written down with error rates and refusal rules | 1, 2 |
| 3 | 16 November | 3 | The arithmetic: the 3-second target is impossible as written; the estimator and the numbers sheet | 3 |
| 4 | 23 November | 4 | The rung of the ladder for Ask and for Cases; ADR-001 | 4 |
| 5 | 30 November | 5 | The data path: ingestion, storage, redaction, lineage | 5 |
| 6 | 7 December | 6 | Permissioned retrieval, scored for leaks | 5 |
| 7 | 14 December | 7 | The model gateway: routing, cascade, fallback, budgets | 6 |
| 8 | 21 December | 8 | The Ask workflow, sessions, memory and the job queue | 6 |
| 9 | 28 December | 9 | The bounded Cases agent over MCP, with approvals and an audit trail | 6, 7 |
| 10 | 4 January 2027 | 10 | The service, caching, deployment shapes, isolation, CI | 6 |
| 11 | 11 January | 11 | The 22 September incident replayed and designed out | 9 |
| 12 | 18 January | 12 | Trust boundaries mapped and attacked | 7 |
| 13 | 25 January | 13 | Eval gates and tracing | 8 |
| 14 | 1 February | 14 | Ten times the traffic; an embedding migration | 10 |
| 15 | 8 February | 15 | Rehearsing the design review; practice designs | all |
| 16 | 15 February | 16 | Capstone: the design review with Helen and Gareth | all |
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 isnorthdesk/; tests live intests/; evaluation inevals/; the web service inservice/; design documents indocs/. northdesk/data.pyholds blocks 1 to 6 of the shared data, copied exactly.northdesk/llm.pyholds the setup cell from course-guide/02 andask(model, messages), created once (Module 4 lab).- Every component that calls a model goes through the model port,
ModelPort.complete(messages, tier) -> Completioninnorthdesk/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 carriestenant_id,feature,tierandbudget_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, statusanswered,not_foundorprovider_error). - Every span from Module 9 on has
trace_id,span_id,parent_id,name,start_ms,duration_msandattrs, the same shape asTRACES. docs/numbers.mdis generated bynorthdesk/estimate.pyand 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.
| Module | Lessons add | The lab adds |
|---|---|---|
| 1 | northdesk/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 |
| 2 | docs/problem-frame.md (generated), evals/grade.py, evals/quality_gate.py, northdesk/design_check.py, northdesk/requirements.py, northdesk/residency.py, northdesk/rules.py | docs/requirements.md, tests/test_requirements.py |
| 3 | northdesk/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.py | docs/adr/ADR-000-ask-streams.md, docs/numbers.md (generated), northdesk/estimate.py, tests/test_estimate.py |
| 4 | northdesk/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.py | docs/adr/ADR-001-architecture-rung.md, northdesk/ladder.py, northdesk/llm.py, tests/test_ports.py |
| 5 | northdesk/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.py | docs/adr/ADR-002-data-path.md, northdesk/datapath.py, northdesk/governance.py, northdesk/intake.py, northdesk/sources.py, tests/test_ingest.py |
| 6 | evals/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.py | docs/adr/ADR-003-retrieval-pipeline.md, evals/retrieval_eval.py, northdesk/answer.py, northdesk/retrieve.py, tests/test_retrieve.py |
| 7 | docs/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.py | docs/adr/ADR-004-model-layer.md, northdesk/model_registry.py, northdesk/requestpath.py, tests/test_gateway.py |
| 8 | docs/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.py | docs/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 |
| 9 | evals/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.py | docs/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 |
| 10 | evals/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 |
| 11 | northdesk/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.py | docs/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 |
| 12 | docs/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.py | docs/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 |
| 13 | evals/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.py | docs/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 |
| 14 | northdesk/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.py | docs/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 |
| 15 | docs/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.py | docs/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 |
| 16 | 15 February | (the capstone) |
The design artifacts
Design documents are part of the deliverable, not decoration. Each is written from measured numbers.
| Artifact | What it holds | Rule |
|---|---|---|
docs/design.md, then DESIGN.md | The design in the roadmap's ten-step order | Every number in it comes from docs/numbers.md or a measured run |
docs/numbers.md | Traffic, tokens, storage, GPU memory, cost per request, per tenant and per month, the latency budget per hop | Generated by northdesk/estimate.py; never edited by hand |
docs/adr/ADR-NNN-title.md | One decision each | Uses the template below |
| Data path and request path figures | Where content comes from and how it stays fresh; every hop from question to answer, annotated with latency and cost | Drawn 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.md | Every untrusted input, what it can reach, the control on each path | Checked by evals/control_checks.py |
docs/failure-modes.md | Component, failure, how it is detected, fail-open or fail-closed, fallback, blast radius | Each row has a test in tests/test_faults.py or says why not |
docs/migrations.md | How to change the embedding model, the vector store and the provider without downtime | Costed with the estimator |
The ADR template, used from Module 4 on:
# 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 usehris_get_employee(dots become underscores, Module 9 Lesson 2), and Module 9 Lesson 3's tool gateway uses<server>__<tool>,hris__get_employee.