[{"slug":"building-ai-slack-assistant","title":"How I Built an AI Slack Assistant That Engineers Actually Use","description":"The architecture, decisions, and lessons learned from building an AI-powered Slack bot that helps engineers navigate a large codebase — without hallucinating.","date":"2025-03-05","category":"note","tags":["ai-engineering","slack-api","rag","python"],"featured":true,"readingMinutes":3,"words":755,"issueNumber":3,"issueUrl":"https://github.com/nnamanikester/kescript/issues/3","reactionCount":0,"commentCount":0,"text":"At Turing, engineers spend a non-trivial amount of time just trying to understand the codebase — where something lives, how a feature works, why a decision was made. I built a Slack app to fix that. Here's how. The Problem Our codebase is large. Senior engineers know it well, but junior engineers and new hires constantly need to ask: \"Where does X happen?\", \"How does Y work?\", \"What does this service do?\" The answers are in the code. But grep and ctrl+F have a ceiling. What if you could just ask? Architecture Overview Simple on paper. The details are where it gets interesting. Building the Codebase Index The first step is turning the codebase into something searchable by meaning, not just keywords. Chunking Strategy I tried several approaches: By file — Too coarse. Large files produce chunks too big for context windows. By fixed token count — Fast and simple, but splits functions/classes mid-way, losing context. By AST (syntax tree) — Best quality, but complex to implement across languages. I landed on a hybrid: chunk by function/class boundaries using tree-sitter when possible, fall back to 512-token overlapping chunks otherwise. Embeddings I used OpenAI's text-embedding-3-small. It's cheap, fast, and good enough. I store embeddings in Chroma (local, no infra overhead). Keeping the Index Fresh A stale index is worse than no index — it confidently answers with outdated information. I added a Git hook to re-index changed files on commit: The Slack Integration Slack's Events API posts to a webhook URL when users mention your bot. I use FastAPI to handle events: The Prompt That Actually Works Getting the prompt right took iteration. The naive version hallucinates confidently: The version that works: The key change: explicit instruction to say \"I don't know\" when the answer isn't in the context. Without this, LLMs will confidently make things up from their training data. Handling Irrelevant Retrieval Sometimes the vector search returns code that's vaguely related but doesn't actually answer the question. I added a relevance filter: What I'd Do Differently Re-ranking — After vector search, run a second pass with a cross-encoder to re-rank results. Much better relevance, small latency cost. Conversation memory — Currently each question is stateless. Adding conversation history would allow follow-up questions. User feedback loop — A 👍/👎 reaction on responses would generate labeled training data for evaluation and improvement. Better chunking metadata — Include the file path and surrounding function names in each chunk. This context dramatically improves LLM answers. Results After 3 months in production: 200+ questions answered per week Engineers explicitly say it reduced their time searching for code New hires onboard faster — they don't have to wait for a senior to explain architecture The best feedback: \"I use it more than Stack Overflow for codebase questions.\" That's the signal that made it worth building."},{"slug":"transitioning-to-ai-engineering","title":"From React Native to AI Engineering: My Transition Roadmap","description":"Why I'm moving from mobile engineering to AI, what I'm learning, and the concrete path I'm taking — including the tools, concepts, and projects that are building the bridge.","date":"2025-02-10","category":"note","tags":["ai-engineering","career","learning"],"featured":true,"readingMinutes":4,"words":939,"issueNumber":5,"issueUrl":"https://github.com/nnamanikester/kescript/issues/5","reactionCount":4,"commentCount":0,"text":"Six years ago I wrote my first React Native component. Today I'm building AI-powered tools at Turing and studying LLMs in every spare hour I have. This post is about why, and how. Why the Shift? A few months into my role at Turing, I was tasked with building an AI-powered Slack assistant for our engineering team. The idea was simple: let engineers ask questions about the codebase in natural language and get useful answers immediately. I shipped it using a basic RAG (Retrieval-Augmented Generation) pattern — embed the codebase, vector search for relevant chunks, pass to an LLM with context. The result was genuinely useful. Engineers started relying on it daily. That moment crystallized something for me: the leverage that AI gives a single engineer is extraordinary. As a mobile engineer, I build features that serve users. As an AI engineer, I can build tools that multiply the productivity of entire teams. The leverage is different in order of magnitude. What AI Engineering Actually Means (To Me) I've seen \"AI engineer\" mean everything from \"prompted GPT-4 in a product\" to \"trained a transformer from scratch.\" My definition, which I think is most useful for software engineers: An AI engineer builds systems that use LLMs and ML models as components — designing the data flow, prompts, retrieval strategies, evaluation, and infrastructure around them. It's not research. It's not pure ML. It's software engineering applied to AI capabilities. The Learning Stack Here's what I'm actually studying, in order of priority: 1. Prompt Engineering (Foundation) Before touching code, understand how LLMs respond to instructions. The key concepts: System prompts — Set the context and persona Few-shot examples — Show the model what good output looks like Chain-of-thought — Ask the model to reason step by step Output formatting — Structured outputs (JSON mode, tool calls) 2. RAG (Retrieval-Augmented Generation) This is where I spent most of my early time. RAG lets you give an LLM access to your own data: The tricky parts aren't the code — they're the decisions: What's your chunking strategy? (size, overlap, by sentence vs paragraph?) What embedding model? (OpenAI ada vs open source vs domain-specific?) How do you handle context window limits? How do you evaluate retrieval quality? 3. Agents and Tool Use This is the most powerful pattern. Instead of one LLM call, you give the model tools it can call: Agents loop: LLM decides action → execute action → feed result back → LLM decides next action → repeat until done. 4. Evaluation The hardest problem in AI engineering. How do you know if your system is getting better or worse? I'm using a combination of: LLM-as-judge: Use a strong model to evaluate outputs from a weaker model Human evaluation: Still the gold standard for important decisions Metrics: BLEU/ROUGE for summarization, custom evals for task-specific behavior The Cross-Domain Advantage Here's what I've realized: being a strong software engineer first is a massive advantage in AI engineering. Mobile engineers are obsessed with performance. We profile everything. We care about latency, memory, battery. That mindset maps directly to AI systems: Latency — Streaming responses, parallel calls, caching embeddings Memory — Chunking strategies, context management Reliability — Fallbacks, retry logic, error handling When I built the Slack assistant at Turing, my mobile engineering instincts immediately kicked in: Cache frequently-accessed embeddings (like caching API responses in React Query) Stream responses to avoid perceived latency (like skeleton loading states) Graceful degradation when the LLM returns garbage The skills transfer more than you'd think. What I'm Building Next To cement AI Engineering skills, I'm working on: Personal codebase assistant — RAG over my own projects, queryable via CLI Blog post generator — Given a topic and outline, generates a draft I can edit Code review bot — GitHub Action that reviews PRs using GPT-4o All three are tools I'd actually use. The best way to learn AI engineering is to build things you'll use every day. Resources That Actually Helped fast.ai — Practical deep learning from scratch, no fluff Anthropic's documentation — Best prompt engineering guides available LangChain cookbook — Real patterns, not toy examples Building LLMs for Production (book) — End-to-end systems thinking Latent Space podcast — Keeps me current on what's happening The Long Game I'm not trying to replace my software engineering background — I'm extending it. The engineers who will be most valuable in the next decade are those who can both build robust systems and integrate intelligent capabilities into them. React Native taught me how to think about cross-platform constraints, performance budgets, and user experience. AI engineering is adding a new dimension: systems that can reason, plan, and adapt. The transition isn't a career pivot. It's an upgrade."},{"slug":"react-native-performance-optimization","title":"React Native Performance: What I've Learned Shipping 5 Production Apps","description":"The real bottlenecks in React Native performance and how to fix them — from JS thread congestion to render optimization and memory leaks.","date":"2025-01-15","category":"note","tags":["react-native","performance","mobile"],"featured":true,"readingMinutes":3,"words":632,"issueNumber":4,"issueUrl":"https://github.com/nnamanikester/kescript/issues/4","reactionCount":0,"commentCount":0,"text":"After six years of building React Native apps — from logistics platforms to meditation apps — I've seen performance fail in the same ways, over and over. This is the distilled knowledge from shipping 5 production apps. The JS Thread Is Your Bottleneck React Native runs your JavaScript on a single thread. Every animation, every state update, every re-render competes for that thread. The moment you block it, your UI stutters. The golden rule: keep the JS thread free during animations. useNativeDriver: true moves the animation to the native thread entirely. No JS thread involvement. No jank. Reanimated 3: The Right Tool for Complex Animations For anything beyond simple Animated API, reach for Reanimated. Worklets run on the UI thread synchronously: The useAnimatedStyle worklet runs entirely on the UI thread — zero JS overhead. FlatList: The Single Biggest Win Replacing ScrollView with FlatList on a list of 50+ items is usually an instant 2-3x improvement. But FlatList has footguns: getItemLayout is the secret weapon. It skips layout measurement for every item, which is expensive. If your list items have a fixed height, always implement this. Memory Leaks in Subscription Patterns This one bit me hard at Maharishi — meditation timer sessions that would leak memory over time: Same applies to AppState, NetInfo, and any other subscription. The rule: if you subscribe, you must unsubscribe. Hermes Engine Enable it. Now. The performance gains are significant: Hermes pre-compiles JS to bytecode at build time, dramatically reducing TTI (time to interactive). The Profiler Workflow Don't guess at performance problems. Profile them: Flipper — For general JS thread and memory profiling React Native Performance Monitor — Quick FPS and RAM overlay (shake device → Perf Monitor) Android Profiler / Xcode Instruments — For native-level issues why-did-you-render — For identifying unnecessary re-renders in dev Key Takeaways Use useNativeDriver: true for all Animated API animations Use Reanimated for complex gesture-driven animations Always use FlatList for scrollable lists — implement getItemLayout if items are fixed height Clean up all subscriptions in useEffect return functions Enable Hermes — it's a free 10-30% TTI improvement Profile before optimizing — gut feelings about perf bottlenecks are usually wrong"},{"slug":"typescript-patterns-react-native","title":"TypeScript Patterns I Use in Every React Native Project","description":"The TypeScript patterns that have saved me the most time and prevented the most bugs across 6 years of mobile development.","date":"2024-12-20","category":"note","tags":["typescript","react-native","best-practices"],"featured":false,"readingMinutes":2,"words":558,"issueNumber":6,"issueUrl":"https://github.com/nnamanikester/kescript/issues/6","reactionCount":0,"commentCount":0,"text":"TypeScript in React Native isn't just about adding types — it's about designing a codebase that catches entire categories of bugs at compile time. Here are the patterns I reach for consistently. 1. Discriminated Unions for State This is the single most valuable TypeScript pattern for UI state: This pattern eliminates an entire class of \"data is undefined when status is success\" bugs. 2. Typed Navigation with React Navigation Untyped navigation params are a maintenance nightmare. Always type your nav stack: If you mistype a param name or pass the wrong type, you get a compile error — not a runtime crash. 3. Generic API Response Wrapper Instead of typing every API response individually, a generic wrapper: 4. as const for Config Objects When you have config arrays or objects that are used as type sources: 5. Utility Types for Component Props React Native components often share prop shapes. Utility types keep it DRY: 6. Type-Safe EventEmitter Native modules often use event emitters. Type them: 7. satisfies for Config Objects New in TypeScript 4.9 — validates an object matches a type without widening it: The Meta-Pattern All of these patterns share a common philosophy: make impossible states unrepresentable. If your types can represent { status: 'success', data: undefined }, that's a bug waiting to happen. If your navigation types allow navigate('NonExistent'), you'll get a runtime crash. TypeScript is most valuable not when it types the obvious, but when it makes impossible combinations of state literally impossible to write. That discipline — designing types that model reality accurately — is what separates TypeScript that helps from TypeScript that just adds ceremony."}]