Resume Defense Guide
Resume Defense Guide
Line-by-line defense of [[Interview Prep/Resume/Resume_2026.tex|Resume_2026]] (Oct 2026 version). For every resume line: the 30-second pitch, the questions you'll get, what to say, and the traps — the follow-ups that expose a weak answer.
Built from the real code in ~/Documents/Projects/Work + [[Past Experience Q&A For Interview]] + [[AskTGE Work Summary]] + [[ASK_TGE_SYSTEM_DESIGN]]. Where a fact could not be confirmed from code or notes it is marked ⚠️ VERIFY — fix these before your first interview, never improvise them live.
0. Ground Rules (read before every interview)
Confidentiality — names you NEVER say
| Internal name | Say instead |
|---|---|
| TGE / Team Global Express | "a large Australian logistics client" |
| AskTGE | "the enterprise AI assistant" / "the client's multi-agent AI platform" |
| AskSubbie | "the subcontractor-management agent" |
| DataMate / SQLMatic | "a data/analytics agent", "a text-to-SQL agent" |
| AskLegal | "a legal-document agent" |
| agent-core | "our shared agent platform library" |
| Aqchatter, TED devices | "the client's event bus", "in-vehicle telematics devices" |
If asked "who was the client?" → "A large logistics company in Australia — I'm under NDA so I'll keep the name out, but I can go as deep as you want on the engineering." Interviewers respect this; it signals professionalism.
"I" vs "we"
Say "I" for what you built yourself, "we" for team decisions. Interviewers probe ownership hard at senior level. Your honest ownership map:
| Work | Yours | Not yours |
|---|---|---|
| LLM perf audit (14 fixes) | Diagnosis + all fixes | — |
| File editing / generation tools | Built end-to-end | — |
| Shared agent library | Extraction, versioning, migrations | Original agents were written by the team |
| History API + DynamoDB redesign | Schema design, API, encryption change | Principal architect reviewed the design doc |
| ABN compliance | Built end-to-end | — |
| AI Value Platform | Built backend + SPA ⚠️ VERIFY: any co-builders? | — |
| Fleet map | React map, frontend WebSocket client, FastAPI REST endpoints | Backend WebSocket server = teammate |
| Crest ML thresholding | Parallel training layer, per-KPI isolation | ML models (data science team), overall product |
| Splunk.conf24 | Contributor to the featured product | You were NOT the speaker — never say you presented |
Every number has a "how did you measure it?"
If you can't answer how a number was measured, the number hurts you. Each section below has a Numbers box — memorise those, and the measurement method.
Answer shape for any deep-dive: P-S-T-R
Problem (what was broken, with a number) → Solution (what you did, the mechanism) → Trade-off (what it cost / what you rejected) → Result (number). The trade-off step is what separates senior from mid-level answers.
1. Summary → "Tell me about yourself" (90 seconds)
Resume line: Senior Software Engineer with 5+ years building production backend systems and multi-agent LLM applications on AWS Bedrock with async Python (FastAPI), focused on latency, cost and reliability. Previously built ML-driven monitoring for Splunk ITSI, featured at Splunk.conf24.
Script (practise out loud, time it)
"I'm a backend engineer with a little over five years of experience, mostly in Python, and for the last year and a half I've been building production LLM agents on AWS.
Right now I'm at HCLTech working for a large Australian logistics client. We run about five LLM agents on AWS Bedrock — a general assistant, a text-to-SQL agent, a legal-document agent and so on. My work there falls into three buckets. Performance and cost: I did an audit of the main assistant, found 14 bottlenecks and took document analysis from 21 seconds to 6, and cut input-token cost by about 90% with prompt caching. Platform: I pulled the shared runtime of those five agents into one versioned internal library so they stop drifting apart. And agent capabilities: most recently, tools that let the agent edit and generate real Excel and Word files by running model-written code in a sandbox and then verifying the result independently.
Before that I spent four years at Crest Data working on Splunk's IT monitoring product, ITSI — ML-assisted thresholding, performance work, and I contributed to a feature that was showcased at Splunk.conf24.
I'm now looking for a product company where I can own AI and backend systems end-to-end at a larger scale."
Follow-ups
Q: Why are you leaving after ~1.5 years?
"The work has been great — I've shipped more AI systems in the last 18 months than in the four years before. But in a services model I don't own the product roadmap, and the scale is one client's internal users. I want to own a product where the user base and the engineering bar are bigger. I'm not running away from anything; I'm moving toward ownership."
Never: complain about HCL, pay, or the client.
Q: You went from a product-ish company (Crest/Splunk) to services (HCL). Why?
"At Crest I was contracted to Splunk, so it was product engineering on someone else's product. HCL gave me the chance to work on GenAI in production in 2025 — when most teams were still prototyping. That was the trade I wanted then. Now I want that AI experience inside a product company."
Q: What's your title? Resume says Senior Developer but summary says Senior Software Engineer.
"My official title at HCLTech is Senior Developer. The summary describes the role I do — senior backend/AI engineering."
Q: Notice period?
"90 days, with a buyout option after 30 days." (Leverage — say it plainly.)
Q: Expected CTC? → Give a range (55–75 LPA per [[Coach]]), anchor high, never a single number first.
2. Skills Section — every line is fair game
Rule: if it's on the skills list, you must survive 3 follow-up questions on it. Below: the questions most likely per item.
2.1 Languages — Python (Expert), TypeScript, JavaScript, SQL, Bash
"Expert" invites hard questions. Be ready for:
| Q | Answer core |
|---|---|
| GIL — what is it, when does it matter? | Global Interpreter Lock: one thread executes Python bytecode at a time. Matters for CPU-bound work (use multiprocessing / native libs). Doesn't matter much for I/O-bound (threads release GIL on I/O; asyncio is single-threaded anyway). Python 3.13 has an experimental free-threaded build (PEP 703). |
| asyncio vs threads vs processes | asyncio: cooperative, one thread, best for many concurrent I/O waits (HTTP, DB, LLM calls). Threads: preemptive, good for blocking libraries (boto3!). Processes: true parallelism for CPU work. |
What happens if you call blocking code inside async def? | Blocks the whole event loop — every other request stalls. Your war story: sync boto3 S3 presigned-URL calls inside the LangGraph stream loop stalled token delivery → fixed with asyncio.to_thread + asyncio.gather. |
asyncio.gather vs TaskGroup | gather returns results in order; by default one exception propagates but other tasks keep running unless cancelled. TaskGroup (3.11+) cancels siblings on failure — structured concurrency. Mention you'd use return_exceptions=True when one failure shouldn't kill the rest. |
| Generators / decorators | Know yield, async for, writing a decorator with functools.wraps. War story: @require_valid_session decorator replaced 30+ duplicated 5-line validation blocks. |
Mutable default args, is vs ==, dict ordering, __slots__, dataclasses vs Pydantic | Standard Python trivia — revise [[Interview Prep/Content/index |
SQL: window functions (ROW_NUMBER, LAG), indexes (B-tree, composite index column order), EXPLAIN, isolation levels. Expect one live SQL question.
TypeScript/JS: you'll mostly be asked about React (see 2.5). Know event loop (microtasks vs macrotasks), closures, Promise.all.
2.2 AI / LLM — LangGraph, Bedrock AgentCore, Guardrails, Knowledge Bases, MCP, RAG, Multi-Agent, Prompt Caching
Q: LangChain vs LangGraph?
"LangChain gives you building blocks — model wrappers, prompts, tools. LangGraph adds a stateful graph on top: nodes are steps (LLM call, tool execution), edges are transitions, and state is carried across steps. That makes loops — call model → call tools → call model again — explicit and controllable. We build our agents as LangGraph graphs; I actually replaced the default
ToolNodewith a custom node that runs tool calls in parallel withasyncio.gather."
Q: What is Bedrock AgentCore?
"AWS's managed platform for running agents. We use three pieces: Runtime — hosts each agent as a container behind an invocation endpoint, with streaming; Memory — managed conversation memory, which our history service reads from; and Code Interpreter — a managed sandbox for executing code, which is what my file-editing tools run model-written Python in."
Q: Bedrock Guardrails?
"Policy filters applied to model input and output — denied topics, content filters, PII/sensitive-info detection, word filters. One of my perf fixes was scoping: document-analysis requests were running four guardrail policies on large documents; we reduced that to the two that actually mattered for that use case, which cut latency. Guardrails also re-scanned the static system prompt every turn — prompt caching eliminated that."
Q: Bedrock Knowledge Bases / RAG — explain RAG.
"Retrieval-Augmented Generation: instead of relying on what the model memorised, you retrieve relevant chunks from your own documents at query time and put them in the prompt. Pipeline: chunk documents → embed → store in a vector index → at query time embed the question → similarity search → top-k chunks into the prompt → model answers with citations. Bedrock Knowledge Bases manages that pipeline for us; the agent calls it as a tool."
Follow-ups to be ready for: chunk size trade-off (small = precise but loses context; large = more context but noisy and expensive), hybrid search (keyword + vector), re-ranking, how do you evaluate RAG (retrieval recall@k, answer faithfulness/groundedness, a labelled question set), hallucination mitigation (cite sources, instruct "say you don't know", lower temperature, ground in retrieved text).
Q: What is MCP?
"Model Context Protocol — an open standard for exposing tools and data to LLM applications. A server exposes tools with schemas; any MCP-capable client can discover and call them. We reach SharePoint and Redshift through MCP servers behind a gateway, and the user's JWT is forwarded so the agent only sees what that user is allowed to see."
Trap: "Why MCP instead of just writing a function tool?" → "Reuse and decoupling: the same SharePoint MCP server serves several agents, auth is centralised at the gateway, and tool definitions live with the data owner, not copied into each agent."
Q: Multi-agent — how is your system multi-agent?
"There's a front-end web app that routes a user to one of several specialised agents — general assistant, text-to-SQL, legal, subcontractor management — each its own AgentCore runtime with its own tools and prompts. Inside an agent, the LangGraph loop decides which tools to call."
Be honest: it's routing to specialist agents, not agents negotiating with each other. Don't oversell into "autonomous agent swarm".
Q: How does prompt caching work? → full answer in §3.1.
2.3 AI-Assisted Dev — Claude Code, Kiro, Cursor, GitHub Copilot; agentic & spec-driven development; custom skills/hooks
Very likely question in 2026. They want to know you use AI with judgement, not blindly.
Q: How do you use AI coding tools day-to-day?
"Different tools for different jobs. Copilot/Cursor for in-editor completion and quick refactors. For bigger features I use agentic tools — Claude Code or Kiro — but spec-first: I write or generate a plan document first (requirements, the files involved, decisions, test plan), review it, and only then let the agent implement against it. The ABN compliance feature, for example, started as a self-contained implementation plan with a repository map and locked decisions before any code was written.
I also customise them: in my own setup I have skills — reusable instruction packs for recurring tasks — and hooks, e.g. a hook that blocks a task from finishing until a log file is updated."
Q: How do you make sure AI-written code is correct?
"Same bar as human code: I review every diff, I require tests, and I don't merge what I can't explain. Concretely — the ABN feature went through a dedicated review pass that found 7 real bugs, like a failed run that could still overwrite the baseline, and pagination that silently re-read page one. I treat the agent as a fast junior: great throughput, needs review."
Q: What's spec-driven development? → Requirements + design + task list written before code (Kiro formalises this as specs). Benefit: the agent has a contract; you review intent before reviewing code.
Q: Risks? → Plausible-but-wrong code, security (secrets, injection), licence issues, skill atrophy, over-large diffs. Mitigations: small PRs, tests, review, never paste secrets.
2.4 Backend & Cloud
| Item | Likely question | Answer core |
|---|---|---|
| FastAPI vs Flask vs Django | When would you pick each? | FastAPI: async-native, Pydantic validation, OpenAPI for free — APIs/services (History API). Flask: minimal, sync, great thin backend (AI Value Platform: 4 jobs only — SSO, authz, DynamoDB API, Jira proxy). Django: batteries-included (ORM, admin, auth) — full apps (Crest MR Analysis app). |
| asyncio | see 2.1 | |
| SSE vs WebSockets | Why SSE for LLM streaming? | SSE: one-way server→client over plain HTTP, auto-reconnect, works through proxies/ALB easily — perfect for token streaming. WebSockets: bidirectional, needed when client also pushes frequently. LLM output is one-way → SSE. |
| ECS/Fargate | Why Fargate not EC2/EKS? | Serverless containers — no node management, per-task billing, fine for a handful of services. EKS only pays off with many services + a platform team. |
| Lambda | When not Lambda? | 15-min limit, cold starts, long-lived streaming connections → containers instead. |
| DynamoDB | see §3.4 | |
| S3 | Presigned URLs — why? | Browser downloads directly from S3 with a short-lived signed URL (we use 300 s TTL); no proxying bytes through the app; no public bucket. |
| KMS | Envelope encryption? | KMS encrypts a small data key; the data key encrypts the payload locally (AES-GCM). Avoids KMS 4 KB limit and per-byte KMS calls. See §3.4 for why History API moved to local Fernet. |
| Redshift | What did you do with it? | Accessed by the data agent via MCP. ⚠️ Keep this light — you didn't administer Redshift. If pushed: columnar warehouse, OLAP, sort/dist keys. |
| Docker | Multi-stage builds, slim images, non-root user, layer caching (copy lockfile → install → copy code). | |
| Kubernetes | ⚠️ VERIFY you can defend. Your deployments are ECS, not K8s. If you only know K8s basics, either remove it or be ready for: Pod/Deployment/Service, readiness vs liveness probes, HPA, ConfigMap vs Secret. | |
| GitLab CI/CD | How do you publish the shared library? | Tag vX.Y.Z → CI job builds the wheel → uploads to GitLab Package Registry using the pipeline job token; consumers install with uv. Agents deploy as ARM64 containers to ECR via GitLab CI. |
2.5 Data & Other
| Item | Prep |
|---|---|
| PostgreSQL | ⚠️ VERIFY — the original note said it was added as a "SQL depth signal". If you have no production Postgres, remove it or be ready for: MVCC, indexes (B-tree, GIN), EXPLAIN ANALYZE, transactions/isolation, connection pooling. |
| MySQL | Fleet system used MySQL for last-known locations. Indexes, InnoDB, replication basics. |
| MongoDB | Document model; when you'd pick it vs DynamoDB (flexible queries/aggregations vs predictable single-digit-ms key access). |
| React / Next.js | Re-render rules, useMemo/React.memo/useCallback, keys, state batching (fleet map), SSR vs SSG vs CSR (Next.js — your portfolio). |
| OAuth2 / Entra ID | Authorization Code flow (+PKCE); ID token vs access token; group-based authorization (AI Value Platform checks Entra group membership; the agents forward the user's Bearer token to MCP gateways). |
| Pytest / Jest | Fixtures, mocking boto3 (moto or stubs), fake timers in Jest for the batching logic. |
| Splunk ITSI | What is it? → IT Service Intelligence: models services from KPIs, computes service health scores, thresholds KPIs to raise episodes/alerts. |
3. HCLTech — Senior Developer (Apr 2025 – Present)
Context line to open any HCL answer: "I'm at HCLTech on a large Australian logistics client. We run about five LLM agents on AWS Bedrock AgentCore, fronted by a Flask web app, deployed on ECS Fargate."
3.1 LLM Performance & Cost
Fixed 14 bottlenecks in an enterprise AWS Bedrock multi-agent assistant — document-analysis latency 21s → 6s, SSE bandwidth 50× lower via delta-only streaming, input-token cost −90% via prompt caching, parallel tool calls via asyncio.gather.
Numbers: 14 issues · doc analysis ~21s → ~6s (~70%) · KB query ~20s → ~14s (~30%) · SSE ~150 KB → ~3 KB per 1,000-token response (50×) · input-token cost on turn 2+ ~90% lower · ~3,100 static tokens cached · cold start 700 ms–1.5 s removed.
How measured: ⚠️ VERIFY and make this concrete — e.g. "same set of test prompts, end-to-end timing from request to final SSE event in staging, before vs after; token counts from Bedrock usage metadata; bandwidth from the response payload size."
30-second pitch:
"Users said the assistant was slow — document analysis took around 20 seconds. Instead of guessing I audited the whole request path end-to-end and found 14 separate problems across the web layer and the agent backend. The four biggest: the agent was running knowledge-base searches it didn't need on uploaded documents; we weren't using prompt caching, so every turn reprocessed ~3,000 static tokens and re-ran guardrails on them; tool calls ran sequentially; and the streaming layer re-sent the whole answer on every token. After the fixes document analysis went from 21 to 6 seconds, follow-up turns cost about 90% less in input tokens, and streaming bandwidth dropped 50×."
The 14 fixes (know at least 8):
| # | Problem | Fix |
|---|---|---|
| 1 | SSE re-sent full accumulated text every chunk → O(N²) | Delta-only forwarding with last_sent_len offset → O(N) |
| 2 | Agent KB-searched content already in the uploaded document | Prompt tells model not to search attached content; removed a brittle hard-block in routing |
| 3 | No prompt caching | cachePoint after system prompt and after tool definitions |
| 4 | Sequential tool execution | Custom parallel tool node with asyncio.gather |
| 5 | Cross-region inference latency | Model confirmed available in local region (ap-southeast-2), switched model ID |
| 6 | Sync S3 presigned-URL calls blocking the event loop mid-stream | Collected citations during stream, enriched after with asyncio.to_thread + gather |
| 7 | Duplicate citation enrichment | Single enrichment path |
| 8 | Verbose logging in prod; regex compiled per call | LOG_LEVEL=WARNING; precompiled regexes at module load |
| 9–10 | Duplicate tool-usage / thinking SSE events | run_id dedup guard; send first thinking event only |
| 11 | LLM + graph built at import → slow cold starts | Lazy initialisation on first request |
| 12 | System prompt sent twice (~1,400 extra tokens) | Strip incoming SystemMessage; one node owns the prompt |
| 13 | 4 guardrail policies on doc analysis | Reduced to the 2 relevant ones |
Deep-dive Q&A
Q: Which fixes moved the 21 → 6 seconds?
"Mostly three: removing the unnecessary knowledge-base search on documents — that removed a whole tool round-trip plus another model call; moving to the in-region model endpoint; and reducing guardrail policies on large document payloads. Caching helped follow-up turns more than first turns." ⚠️ VERIFY this attribution against your notes/measurements — if you measured per-fix, quote it.
Q: Explain prompt caching. Why 90%?
"The model provider can snapshot the processed prefix of a prompt. You mark a cache point; on later requests with the identical prefix, those tokens are read from cache instead of reprocessed. Cached reads are billed at a fraction of normal input price — about a tenth — so for our ~3,100 static tokens of system prompt and tool definitions, follow-up turns cost roughly 90% less on that portion. I placed two cache points: after the system prompt and after the tool list."
Traps:
- "What breaks the cache?" → Anything that changes in the prefix: a timestamp, user name, or reordered tools placed before the cache point. Rule: static content first, dynamic content after.
- "Any minimum?" → Yes, providers require a minimum prefix length (around 1,024 tokens for Sonnet-class models) and the cache has a short TTL (~5 minutes, refreshed on hit). Cache writes cost slightly more than normal input, so caching only pays off when the prefix is reused.
- "Is 90% the total bill?" → No — it's input-token cost on follow-up turns. Output tokens and the first turn are unchanged. Be precise; overclaiming here is a classic trap.
Q: Explain the O(N²) streaming bug.
"The middleware forwarded the accumulated response on every chunk. Token 500 sent 500 tokens' worth of text, token 501 sent 501 — total bytes grow quadratically, and the browser re-rendered the whole message each time. I kept an offset of what had already been sent and only forwarded
accumulated[last_sent_len:]. Linear bandwidth, and the browser just appends."
Q: Why not just stream tokens directly from the model? → We do stream; the bug was in an intermediate layer that re-assembled and re-forwarded. Lesson: every hop in a streaming pipeline must be delta-based.
Q: Parallel tool calls — any risk?
"Only for tools with side effects or ordering dependencies. Our parallelised tools were read-only searches, so running them concurrently is safe. For a tool that writes, I'd keep it sequential or make it idempotent. Also bound concurrency if tools hit a rate-limited API."
Q: How did you find 14 problems — what was your method?
"Followed one request end-to-end through every layer — browser, Flask gateway, SSE middleware, agent runtime, LangGraph nodes, tools, Bedrock — logging timestamps and payload sizes at each hop, and reading the code at each hop for things like blocking calls, duplicate work, and per-request initialisation."
Q: What would you do next? → Observability (per-stage latency metrics/traces instead of ad-hoc logs), an eval set so prompt changes don't regress quality, response streaming of tool progress, semantic caching for repeated questions.
3.2 AI File Editing & Generation ⭐ strongest new story
Built agent tools to edit uploaded Excel/CSV/Word files and generate new XLSX/DOCX; LLM-written Python runs in the sandboxed AgentCore Code Interpreter, and an independent verifier reports measured changes, not model claims.
Numbers / config: formats XLSX, CSV, DOCX · max 2 attempts per edit (retry with traceback) · 10 edits per session cap · sandbox timeout 600 s · download URL TTL 300 s · feature-flagged (FILE_EDITING_ENABLED, FILE_GENERATION_ENABLED). Usage numbers ⚠️ VERIFY (shipped Oct 2026 — likely too new for adoption numbers; say so).
30-second pitch:
"Users wanted to say 'add a total column and sort by date' to a spreadsheet they uploaded, and get a real file back. LLMs can't safely emit binary files, so the agent generates a Python script instead, that script runs in an isolated AgentCore Code Interpreter sandbox, and then — the important part — a fixed verifier script that I wrote compares the original and the result and reports what actually changed. The user sees measured row and cell counts, not the model's claims. The original upload is never modified; every edit is a new versioned file you can keep editing."
Architecture (draw this on the whiteboard):
flowchart LR
U[User upload] --> S3[(S3)]
A[Agent tool call<br/>edit_uploaded_file] --> P[Profile file<br/>sheets, rows, sample cells,<br/>CSV dialect, at-risk parts]
S3 --> P
P --> G[LLM writes<br/>Python edit script<br/>+ manifest]
G --> X[AgentCore Code Interpreter<br/>writeFiles → run → readFiles]
X -->|traceback| G
X --> V[Fixed verifier script<br/>diff original vs result]
V --> D[(New document<br/>edits/ prefix<br/>parent pointer, version)]
D --> E[SSE file event<br/>→ file card + presigned URL]
Deep-dive Q&A
Q: Why have the model write code instead of returning the edited data?
"Three reasons. Binary formats — the model can't emit a valid .xlsx. Size — a 2 MB workbook is ~2.7 MB of base64, which would blow the context and cost. And fidelity — code operating on the real file preserves everything the model didn't touch. The model only ever sees a profile of the file — sheet names, dimensions, a sample of rows — never the full bytes or the download URL."
Q: Why the independent verifier? Isn't the model's summary enough?
"The model's manifest says what it intended to do. A user can't eyeball a 40 MB workbook to check. A made-up count is worse than no count. So the verifier is a fixed script — not model-written — that loads both files and measures rows, cells, formulas and style changes. If the model says 'added 120 rows' and the verifier finds 0, we know. An empty result is treated as a failure and retried, never handed over."
Q: What happens when the generated code crashes?
"We catch the traceback and feed it back to the model for one more attempt — max two attempts. The preamble also clears any half-written output so a retry can't pick up a broken file from the previous attempt."
Q: Security — you're executing LLM-generated code. How is that safe?
"It never runs on our hosts. It runs in AgentCore Code Interpreter, a managed isolated sandbox with a timeout; files go in and out only through explicit
writeFiles/readFilescalls. The original upload is read-only from the tool's perspective — edits are written as new objects under a separate prefix. There's a per-session cap so a loop can't burn resources, and the feature is behind a flag." ⚠️ VERIFY the sandbox network configuration before claiming "no network access".
Follow-up trap: "What about prompt injection inside the spreadsheet?" → "A cell could contain 'ignore instructions and…'. The blast radius is limited to the sandbox and that user's own file; the verifier report shows the real changes; and the user explicitly downloads the result. I'd add stricter sandbox egress rules if it isn't already locked down."
Q: Tell me about a subtle bug you hit.
Two good ones:
- SDK binary corruption. "The SDK's download helper decodes the returned blob as UTF-8 and only falls back to bytes on a decode error. A binary file whose bytes happen to be valid UTF-8 comes back as a string and gets corrupted when re-encoded. I wrote our own sandbox session wrapper that reads the raw blob and always returns bytes. It also replaced the old approach of base64-embedding the file in the generated code, which inflated every payload by a third and only worked one-way."
- Silent data loss on save. "openpyxl drops charts, images and pivot tables when it saves a workbook — even if you didn't touch them. So before running anything I inspect the file's zip parts; if it has those features, the tool stops and asks the user to confirm (
accept_feature_loss). For Word, python-docx preserves unknown parts, but settingparagraph.textwipes per-word formatting and hyperlinks, and text inside unaccepted tracked changes is invisible — so tracked changes require consent too."
Q: How does generation differ from editing?
"Generation builds a new workbook or document from scratch. The key decision: the main agent passes the actual data in — say, numbers from a knowledge-base answer — and the inner model only decides layout and formatting. If you ask a model to 'make a sheet of last quarter's results' with no data, it invents numbers. The rest — sandbox, verifier, versioned storage, file card — is shared with editing. Generated files follow the client's brand styling."
Q: How does the file get to the browser?
"The tool stores the result in S3 and writes a document record; it stashes a pending-file record that a runtime streaming hook picks up and emits as a
fileSSE event. The web app renders a file card and serves a short-lived presigned download URL. Because the result is itself a document, 'now sort it by date' works on it next turn."
Q: Testing? → Unit tests for profiling, feature-loss detection, manifest building, verifier parsing (~470 lines of tests for editing, plus generation and Word suites); route tests in the web app.
Q: What would you improve? → Persist the per-session cap (it's process-local, resets on container restart); support preserving charts via a different library; streaming progress for long edits; eval set of edit instructions with expected verifier output.
3.3 Shared Agent Platform
Extracted the common runtime of 5 production LLM agents (async streaming, hooks, cost tracking, shared tools) into a versioned internal Python package published via GitLab CI; migrated agents incrementally with parity checks.
Numbers: 5 agents depend on it (general assistant, text-to-SQL, legal, data agent, subcontractor agent) · versions 0.1 → 0.11 · publish-on-tag CI.
30-second pitch:
"We had five agents that were all forks of one original codebase, and they'd drifted — same bug fixed in one and not the others, stale pricing data in one, different streaming behaviour. I extracted the shared skeleton — config, cost calculation, storage, event formatting, the streaming runtime, base tools — into one versioned package. Each agent now keeps only its domain layer: prompts, domain tools, settings. I migrated them one at a time with parity checks, closest clones first and the hardest last."
Deep-dive Q&A
Q: How did you decide what goes into the library?
"Started with modules that were byte-identical across agents — zero-risk, e.g. the cost calculator was identical in all four I compared. Then promoted things that had drifted, deciding case by case whether the divergence was a bug or a real feature. One agent — the most-maintained one — was declared the baseline: where agents differ, baseline wins unless the difference is a deliberate feature we promote."
Q: How do agents customise behaviour if the runtime is shared?
"Through hooks on the app factory —
build_apptakes optional callables: assemble the messages, set up per-request scope (e.g. bind the user's JWT for SharePoint), enrich citations, finalise response text, filter streamed chunks, emit tool events, and short-circuit a request entirely. Defaults are pass-through, so adding a hook never changes existing agents. The payoff: the ABN compliance sweep dropped into the subcontractor agent as ashort_circuithook with zero changes to the core."
Q: Versioning strategy?
"Semantic versioning, pre-1.0. Agents pin with compatible-release
~=0.x, so they get patches but not breaking minors automatically. Pushing avX.Y.Ztag triggers CI to build the wheel and publish it to the GitLab package registry using the CI job token — no personal credentials."
Q: Why not a monorepo?
"Each agent is its own repo and its own AgentCore runtime with independent deploys and owners. A package keeps independent release cadence. The cost is version skew — today some agents are on 0.9 and others on 0.11 — which I'd manage with a deprecation window and a regular upgrade cadence."
Q: Something that went wrong? (good failure story)
"Two. First, we bumped consumers to a new version but the tag hadn't been pushed, so CI never published it — installs 404'd. Lesson: release is the tag, verify the registry before bumping consumers. Second, after deleting a duplicated module from one agent, a straggler import still pointed at the old local path and broke that agent. Lesson: after a cutover, grep for every import of the removed module and run the agent's smoke test, not just unit tests."
Q: How did you verify parity? → ⚠️ VERIFY specifics. Safe framing: "Same requests through the agent before and after cutover — compare streamed events and final responses; unit tests in the library; agent smoke tests in staging."
3.4 Chat History & DynamoDB
Built an async FastAPI history service with local Fernet encryption (replacing per-message KMS calls); moved storage to DynamoDB Single-Table Design — session fetch 2s → 200ms, TransactWriteItems for race-safe concurrent writes.
Numbers: 2 s → 200 ms session fetch · zero GSIs · 16-digit zero-padded epoch-ms in sort keys · KMS round-trip ~5–20 ms per call removed · ~300 records per user at current scale.
30-second pitch:
"The chat sidebar took about 2 seconds to load. The old table used random UUIDs as sort keys, so the only way to show 'most recent sessions first' was to fetch everything and sort in application memory — and every message also needed a KMS call to decrypt. I redesigned it as a single-table design where the sort key embeds a millisecond timestamp, so DynamoDB returns sessions already ordered and we just read the top N. I replaced per-message KMS calls with local symmetric encryption. Fetch went from 2 seconds to 200 milliseconds."
Schema (write it on the board):
| Item | PK | SK |
|---|---|---|
| Session metadata | USER#<user_id> | METADATA#AGENT#<agent_id>#TIME#<epoch_ms_16>#SESSION#<session_id> |
| Message | USER#<user_id> | SESSION#<session_id>#MSG#<epoch_ms_16>#<interaction_id> |
- Sidebar:
Query PK=USER#u, SK begins_with METADATA#AGENT#<a>#,ScanIndexForward=False,Limit=N→ newest first. - Conversation:
Query PK=USER#u, SK begins_with SESSION#<s>#MSG#, ascending.
Deep-dive Q&A
Q: Why zero-pad the timestamp? → Sort keys compare as strings. "9" > "10" lexicographically; zero-padding to fixed width makes string order equal numeric order.
Q: The recency is in the sort key — how do you "move a session to the top"?
"You can't update a key, so you delete the old metadata item and put a new one with a fresh timestamp. Done naively, two concurrent writes can both read the old key, both delete, both put — and you get a phantom duplicate session. So it's one
TransactWriteItems: put the new message, put the new metadata item, and delete the old metadata item withConditionExpression attribute_exists(SK). If another writer already moved it, the condition fails, the whole transaction is cancelled, and we retry from a fresh read with backoff."
Traps:
- "Cost of transactions?" → Transactional writes consume 2× WCU, and you need a pre-flight
Limit=1query to find the current metadata key. Acceptable at our volume. - "Why not just store
updated_atas an attribute and use a GSI sorted on it?" → Valid alternative. GSI = extra write cost on every write and eventual consistency on reads; I chose sort-key encoding for zero GSIs and strongly consistent ordered reads. Say it's a trade-off, not the only answer. - "Hot partition?" → All of a user's data is under one partition key. One user's chat volume is tiny relative to partition limits (10 GB, ~1,000 WCU/3,000 RCU). Documented trigger: if any user grows past ~10k sessions or throttles, move messages to
PK=SESSION#<id>. - "400 KB item limit?" → Messages are separate items, so a long conversation never hits it; very long single responses are compressed before encryption.
Q: Why move off KMS? Isn't that less secure?
"KMS was called for every message, so a sidebar load with 50 messages meant 50 network round-trips at ~5–20 ms each, plus KMS request quotas. We moved to local Fernet (AES-based authenticated encryption) with zlib compression before encryption. The trade-off is real: KMS gives managed rotation, hardware-backed keys and CloudTrail audit; with a local key you own rotation and storage. We accepted that for an internal app, and kept the old KMS path readable for legacy data. Next step I'd push: the key belongs in Secrets Manager, not an environment variable, and rotate with
MultiFernet."
Bonus: "Why compress before encrypting?" → Ciphertext looks random and doesn't compress; compress first.
Q: Migration from the old schema without downtime?
"Dual-write: new code writes both formats; a backfill script rewrites existing records into the new format, validated in staging against sort order; then cut reads over; then remove the old write path." ⚠️ VERIFY that this was executed exactly this way (the notes describe it as the design).
Q: What does the History API itself do? → Async FastAPI service: list a user's sessions (per agent and across agents), session summaries, fetch one session's messages, delete a session; reads from AgentCore Memory + DynamoDB, decrypts, returns Pydantic-typed responses; structured JSON logging.
3.5 Compliance Automation (ABN)
Automated ABN checks for ~2,000 subcontractors — async sweep of SharePoint records (via MCP gateway) against the Australian Business Register API, diffed against a baseline to surface only exceptions.
Numbers: ~2,000 subcontractor records · each checked for: ABN cancelled, entity name/type changed, GST registration lost · replaced manual lookups by compliance staff (planning estimate: thousands per year — ⚠️ VERIFY real usage).
30-second pitch:
"Compliance staff were manually re-checking about 2,000 subcontractors' business numbers on a government website to confirm each one was still active, unchanged and GST-registered. I built a one-click check: it pages through the subcontractor list in SharePoint, looks up each number on the Australian Business Register API, compares against the last stored state, and shows only exceptions — cancelled numbers, changed entity details, lost GST."
Deep-dive Q&A
Q: This sits inside an LLM agent — did the LLM do the checking?
"Deliberately no. It's plain deterministic async code. An LLM loop has a tool-call limit, a fixed output format and costs tokens per row — none of that works for 2,000 records, and compliance needs repeatable results. It's exposed through the agent platform as a short-circuit hook, so it reuses the agent's auth and plumbing without going through the model."
Q: Architecture — who does what?
"Clean split: the agent fetches and validates — it's stateless, returns current registry state. The web app stores, diffs and renders — it owns the DynamoDB baseline, computes exceptions and shows the report. Keeping state out of the agent means the agent stays simple and horizontally scalable."
Q: Why on-demand and not a nightly cron?
"SharePoint is reached with the calling user's token, forwarded through the MCP gateway — there's no service credential yet, so a headless job can't authenticate. That's a prerequisite, not a code task. Phase 1 is a button; a scheduled run is Phase 2 once there's a service identity."
Q: Tell me about bugs you found in review. (great "quality" story — pick 2–3)
- Pagination silently re-reading page 1. "We paged with
$skip, but nobody had ever paged past page 1, and some SharePoint APIs ignore$skip. If ignored, every page returns rows 0–99, it never ends early, and it looks like success — 20,000 rows, inflated exceptions, and partners on later pages never checked. I added a seen-set of row identities: a page with zero new rows raises; partial overlap logs and dedups."- Failed run advancing the baseline. "A failed sweep could still overwrite the stored baseline, so the next run would diff against bad data. Now the baseline only advances on a complete, successful run."
- Zero rows = 'all clear'. "An empty sweep (e.g. auth or column-name problem) rendered as 'no exceptions'. For compliance that's the worst failure mode. Now zero rows is an error."
- Standing issues disappearing. "A cancelled ABN showed once, then vanished next run because it was no longer a change. Compliance needs to see standing non-compliance until it's fixed — so we persist and keep showing open exceptions."
- Report panel going blank while a run was in progress (pointer written at start, run item only at end) → show last complete run; unbounded exceptions list near DynamoDB's 400 KB item limit → capped; auth gate was a whole group in every environment → narrowed.
Q: Rate limits / performance? → Duplicate ABNs are de-duplicated before lookup; SharePoint gateway timeouts are retried with the failing step named in the error. ⚠️ VERIFY the concurrency limit used for ABR calls before quoting a number.
Q: Registry API detail? → Requires a registered GUID (manual human registration — the long pole of the project); JSON endpoint returns JSONP, so we strip the callback wrapper before parsing.
3.6 AI Portfolio Platform
Built an AI governance app (Flask, Entra ID SSO, DynamoDB, Jira sync, ECS Fargate) tracking 114 AI use cases — scoring, benefits, adoption and Bedrock cost.
Numbers: 114 AI use cases · 18 pages · 8 scoring dimensions (1–5) · 77 tests · ECS Fargate, ALB path-routed in the existing cluster.
30-second pitch:
"Leadership needed one place to prioritise and track the company's AI use cases — scoring, expected value, benefits actuals, adoption, Bedrock spend, risk registers. I built it as a vanilla-JS single-page app on a thin Flask backend. The backend does only four things the browser can't: Entra SSO, group-based access control, the DynamoDB API, and a Jira proxy so no Jira token ever reaches the browser."
Q&A
- Why vanilla JS, not React? → No build step, tiny team, mostly forms and charts; deploys as static files behind Flask. Trade-off: less structure as it grows.
- Jira sync design? → Runs nightly in CI, not in the container. Jira owns descriptions/status; human scores are never overwritten by the sync. Every use case is keyed by its Jira issue key — the join across all data.
- DynamoDB modelling? → One item per record, not one blob per collection — avoids the 400 KB limit and lost updates between different records.
- Concurrency? → Known limitation: two people editing the same record is last-writer-wins. Fix: optimistic locking with an
updated_atcondition check. Say this before they find it. - Security? → CSRF: every mutating API call requires a custom header the SPA adds; local-dev auth bypass cannot be enabled in staging/prod ("a runtime-togglable auth bypass is an auth bypass"). Weakness to own: some secrets still in env files → should move to Secrets Manager.
3.7 Fleet Modernisation
Replaced a Flash-era fleet system with React + FastAPI (dispatch, driver profiles, PDF manifest/POD) and a live WebSocket map tracking 1,500+ drivers in real time.
Numbers: 6 states × 250–300 drivers = 1,500+ drivers · telemetry every 5 s · sub-second UI update.
Ownership — say this precisely: you built the React map + frontend WebSocket client and the FastAPI REST endpoints for driver metadata and initial load. A teammate built the backend WebSocket service. Full STAR in memory star_websocket_geo; deep Q&A in [[Past Experience Q&A For Interview]].
Pitch:
"Dispatchers were tracking drivers in a 20-year-old Flash desktop app. I built the replacement live map in React. On load it fetches every driver's last known location over REST, so the map is never blank, then subscribes over WebSocket for 5-second updates. Driver state is a dictionary keyed by driver ID for O(1) updates, updates are batched into one render every ~500 ms, and markers are memoised so only the pins that moved re-render."
Q&A (short — details in the linked note)
- Out-of-order packets → compare packet timestamp to last seen; drop older.
- Disconnect → on reconnect, re-fetch last known locations to fill the gap.
- Stale drivers → no update in 30 s → mark stale (grey pin), keep last position.
- GPS noise → reject impossible implied speed; smooth.
- Scale to 50k drivers? → Kafka between devices and consumers; Redis GEO for live queries; MySQL for history/audit; regional WebSocket gateways; send clients only drivers in their viewport/region.
- Trap: "How did you handle 1,500 concurrent connections on the server?" → "That part was my teammate's service — I can tell you how the client consumed it and how I'd scale the server side." Don't bluff.
The other half of this bullet (driver profiles, job creation, dispatch scheduling, PDF manifest & proof-of-delivery generation) → be ready to explain how PDFs were generated ⚠️ VERIFY library/approach (server-side template → PDF?).
4. Crest Data — Splunk ITSI (May 2021 – Apr 2025)
Opening line: "Crest Data is an engineering services firm; I was contracted to Splunk's ITSI team for four years, working on the product itself."
4.1 ML Integration (Senior SE)
Core contributor to the ML-Assisted Thresholding / Configuration Assistant featured at Splunk.conf24; parallelised multi-KPI model training with per-KPI failure isolation.
Pitch:
"ITSI monitors services through KPIs, and each KPI needs alert thresholds. Setting them manually across hundreds of KPIs is error-prone, so the feature recommends thresholds from historical data using ML. Training ran KPI by KPI, sequentially, and one bad KPI could fail the whole batch. I made training run concurrently across KPIs and isolated failures per KPI — one KPI failing returns an error for that KPI only, the rest succeed."
Q&A
- How did you parallelise? → ⚠️ VERIFY mechanism (thread pool vs async vs Splunk job dispatch). Safe framing: "concurrent requests per KPI with a bounded worker pool, collecting results and errors per KPI".
- Why bounded? → Unbounded concurrency would overload the search head/indexers.
- Per-KPI isolation — why it matters? → Partial success is better than all-or-nothing for a bulk UI; user sees which KPIs failed and why.
- conf24 — what was your role? → "The feature was showcased at Splunk.conf24 by Splunk; I was one of the engineers who built it." Never say you presented.
- The ML part? → Data science owned the models; you owned API contracts, concurrency, UI integration. Async job pattern for slow inference (job ID → poll → apply).
4.2 Performance Optimisation
Profiled Splunk searches, found high-cardinality fields and added targeted indexes — nightly recalculation time −90%.
⚠️ VERIFY — your notes conflict. The resume says indexes on high-cardinality fields; [[Past Experience Q&A For Interview]] says the 90% came from refactoring to bulk processing and fetching only needed fields. Decide which is true (or both) and make the resume and your story match before interviewing.
If it's the indexing story, be ready for:
- What's high cardinality? → Many distinct values (user IDs, entity IDs). Searching/grouping on them without help is expensive.
- What "index" means in Splunk → indexed fields / summary indexing / tstats over accelerated data — know which one you used.
- How measured → nightly job duration before vs after.
4.3 Team Leadership
Led a team of 4 building an MR Analysis app (React, Django) using NLP to auto-categorise developer feedback.
Likely behavioural questions: how you split work, a disagreement in the team, how you handled a slower member, how you reviewed code. Prepare one STAR on each. Technical: what NLP model/approach? ⚠️ VERIFY (classifier type, labelled data source, accuracy).
4.4 Full-Stack Engineering (SE)
Built the Service Insights module (React, Python), reducing false-positive alerts for 700+ enterprise customers.
- 700+ = customers using the product, not customers you measured individually. Phrase: "shipped to 700+ enterprise customers".
- How did it reduce false positives? ⚠️ VERIFY — be ready with the mechanism (better grouping/correlation? smarter thresholds?).
- Trap: "By how much?" — if you have no number, say "we didn't have per-customer telemetry; the signal was support tickets and customer feedback."
4.5 DevOps & Quality
Migrated content packs to GitLab CI/CD for 1,000+ users; reached 80% test coverage (Jest, Pytest) and resolved 50+ customer-facing issues.
- Pipeline stages: lint → test → build package → validate (AppInspect for Splunk apps) → publish.
- Is 80% coverage good? → Coverage measures lines executed, not correctness. Focus on critical paths and edge cases; 80% is a useful floor, not a goal.
- 50+ issues → have one specific bug story ready.
5. Achievements & Education
- Top Performer, 4 consecutive years → "What did you do to earn it?" → tie to delivery: owning features end-to-end, conf24 feature, performance work.
- Deep Learning Nanodegree / IBM Data Science → foundations: backprop, overfitting, train/val/test split, precision vs recall. Keep answers short; don't oversell ML research depth.
- B.Tech CE, GPA 9.49 → rarely asked beyond "good".
6. Behavioural Bank — map each question to a story
| Question | Best story |
|---|---|
| Hardest technical problem | File editing (SDK binary corruption, openpyxl silent loss) or LLM perf audit |
| A time you improved performance | LLM perf (21→6s) / DynamoDB (2s→200ms) |
| Disagreement with a teammate / lead | ⚠️ prepare one — e.g. KMS → Fernet trade-off discussion, or deterministic-vs-LLM for ABN |
| A failure / mistake | Shared library: untagged release → 404s; straggler import broke an agent |
| Ownership beyond your role | Shared agent library (nobody asked; you saw drift) ⚠️ VERIFY who initiated |
| Ambiguous requirements | ABN: phase-1 scope decision (on-demand vs cron) driven by the auth constraint |
| Quality / catching bugs | ABN review — 7 fixes, esp. silent pagination and zero-rows-as-success |
| Mentoring / leadership | Crest team of 4 |
| Working with non-engineers | AI Value Platform for leadership; compliance staff for ABN |
| Using AI tools responsibly | Spec-first + review story (§2.3) |
7. Number Cheat Sheet (memorise)
| Claim | Number |
|---|---|
| Experience | 5+ years (since May 2021) |
| LLM perf | 14 issues · 21s → 6s doc analysis · 20s → 14s KB query · 50× SSE (150 KB → 3 KB) · −90% input tokens on follow-ups · ~3,100 cached tokens |
| File tools | XLSX/CSV/DOCX · 2 attempts · 10/session · 600 s sandbox · 300 s URL |
| Shared library | 5 agents · v0.1 → v0.11 · publish on tag |
| History | 2s → 200ms · zero GSIs · 16-digit epoch-ms SK |
| ABN | ~2,000 records · 3 checks (cancelled / entity change / GST) |
| AI platform | 114 use cases · 18 pages · 77 tests |
| Fleet | 6 states · 250–300/state · 1,500+ drivers · 5 s telemetry |
| Crest | 90% nightly recalculation · 700+ customers · 1,000+ users · 80% coverage · 50+ issues · team of 4 · Top Performer ×4 |
8. ⚠️ VERIFY list — resolve before first interview
- How each LLM-perf number was measured; which fixes drove 21→6s.
- Crest 90%: indexes vs bulk-processing — pick the truth, align resume + story.
- Kubernetes and PostgreSQL — keep only if you survive 3 follow-ups each.
- Shared library: who initiated it; how parity was verified.
- Sandbox network config for the Code Interpreter.
- ABN: real usage numbers; ABR call concurrency limit.
- History migration: was dual-write/backfill actually executed?
- AI Value Platform: solo or team?
- Crest: parallelisation mechanism; Service Insights false-positive mechanism; MR Analysis NLP approach.
- Fleet: PDF manifest/POD generation approach.
Related: [[Past Experience Q&A For Interview]] · [[Projects Done]] · [[AskTGE Work Summary]] · [[Coach]] · [[Interview Prep/Job Switch/30 Company Target List]]