Jaeyoung Heo

I build simulation engines in Python.

Hi, I'm Jaeyoung. I work on simulation systems that wrap complex decision-making logic โ€” most of what I do is taking the implicit rules in one person's head and turning them into something another engineer can safely change later. I like code that's fixable when it breaks more than code that looks clever. I tend to look at the numbers before deciding, and I'll roll back weeks of work if the added complexity doesn't prove its value.

Python ยท pandas/NumPy ยท SQLAlchemy ยท ONNX ยท PostgreSQL ยท Dash/Streamlit ยท GitHub Actions

  1. Extensibility before completeness.

    Trying to get it right on the first pass usually costs more than it saves. What matters is whether someone a year later can see where to make a change. I don't abstract until I've seen the same shape at least three times.

  2. Make hidden connections explicit.

    I don't love callback chains or __getattr__ proxies โ€” anything that hides where control actually goes. Code that reads top-to-bottom is easier to debug and easier to extend. The engine's God class decomposition involved removing proxies from 152+ files and replacing them with explicit component paths.

  3. No optimization without measurement.

    I've been wrong about where the hot path is too many times to trust intuition. Profiler first, then decide. When we got a 104ร— speedup on the warehouse loading logic, the decision of what to touch came from measurements, not from "this feels slow."

  4. Prefer changes you can take back.

    Across 1,600+ commits, 11 of them are reverts. That's not a lot, but weeks of work get rolled back when the added complexity doesn't earn its keep. I tried ECS, didn't like how the lookup paths felt, moved to a facade pattern. Over-decomposed a coordinator, didn't pay off, put it back. What ships is the outcome, not the code.

Hardcoded rules โ†’ plugin architecture

Before

Every time a new type showed up, we forked the existing code and modified it. Changing one policy meant touching the entire execution flow.

After

Split into three layers โ€” state, decision-making, execution โ€” and adding a new type became a matter of composing them. There are now 10+ types running on the same framework.

  • State layer: an abstract interface defines the shared contract; type-specific differences live in composition, not inheritance. Nine variants in production
  • Planning layer: an ABC that owns the decision lifecycle. Seven concrete planners (~3,230 LOC) inherit from it
  • Execution layer: template-method pattern. Type-specific executors register dynamically at runtime
  • Didn't try to generalize upfront. Built five types concretely first, and only after the shared lifecycle was obvious did I replace ~1,442 LOC of duplication with ~1,404 LOC of shared infrastructure

Greedy allocation โ†’ multi-resource transactions

Before

The old approach was "assign to whatever's free right now." Nothing atomic โ€” if anything failed mid-way, you were left with inconsistent state and no way to undo.

After

The whole path is now reserved as a single transaction, and any failure rolls back automatically. Three months of stabilization caught 14+ edge cases along the way.

  • A coordinator plus three type-specific reservers handle each resource's constraints independently
  • A path explorer ranks viable routes by state-transition cost
  • Consolidated 20+ scattered trial fields into `_real_values` / `_trial_values` dictionaries with a context-manager pattern
  • Cache failed attempts to avoid re-running them; adapt frequency based on success rate

God object โ†’ explicit ownership + event-driven architecture

Before

One class, 3,800 lines, 114 attributes, six concerns mashed together. State was scattered across singletons, instances, and groups; `__getattr__` proxies hid ownership; callback chains made side effects untraceable.

After

A 226-line core, 19 components, 25 facades. Zero proxies. A 29-line pub/sub event bus fully decouples execution from side effects.

  • Broke it down across nine stages and 80+ commits. Pulled scattered singleton state back into owned instances first
  • Split 114 attributes into 19 component dataclasses (590 LOC); proxies were removed from 152+ files and replaced with explicit component paths
  • Tried ECS first, didn't like how indirect the lookups got, switched to a facade pattern. Also rolled back over-decomposed coordinators (453 LOC, 226 LOC) that weren't earning their complexity
  • -752 / +163 lines net. New features no longer require touching existing flows

Slow hot paths + black box โ†’ optimization + observability

Before

Hot paths were being called 22Mโ€“500M times per run, and when a weird decision came out there was no way to trace why.

After

Profiling-driven hot path optimization, plus tooling to trace decisions back to their cause. I built the observability before the optimization โ€” the whole point was to decide from data, not from gut.

  • Enum caching (22M+ calls), dictionary indexes, local caches, a custom `__deepcopy__` โ€” hot path trimmed where the profiler pointed
  • Gantt chart plus event timeline so I can follow a decision back to its root cause
  • Structured logging with a 105-module enum taxonomy, so slicing logs by subsystem is painless
  • Warehouse loading logic ended up 104ร— faster โ€” that number came from profiling results, not from guessing

Things I've been building lately

aps-rag 1,731 LOC

AST-based code analysis wired into a hybrid retrieval RAG, exposed to Claude Code through 10 MCP tools.

  • Indexes classes, functions, inheritance, and call graphs into three ChromaDB collections
  • 70% semantic + 30% keyword hybrid search
  • A three-tier knowledge base covering 183 internal documents

dooray-mcp 349 LOC

An MCP server that lets AI agents talk to a project management tool directly.

  • Eight tools plus two workflow prompts
  • Async-first, two-layer error handling

auto-issue 762 LOC

Watches git commits and keeps the relevant docs in sync automatically.

  • Commit polling โ†’ file/doc mapping โ†’ LLM generates the update
  • Syncs to git and wiki at the same time; output is idempotent

ALM Agent In progress

A release and process automation agent, unifying changelog, CI, and deploy flows into a single event layer. Still being built.

  • Changelog, release PR, hotfix detection, deploy reports, requirement structuring
  • Polling trigger โ†’ event router โ†’ use-case handlers โ†’ LLM integration

Mentoring & Writing

Python Deep-Dive Series โ€” A Python deep-dive series I wrote for onboarding junior engineers. Less about syntax, more about why the language behaves the way it does โ€” object model, descriptors, iterators, generators, decorators, tied together.

A long-form technical blog on Python internals, compilers, Linux, computer architecture, GPU systems, and MLOps. I tend to sit on one topic for a while. Visit the blog โ†’

Contact

If you work on simulation systems, performance engineering, decision infrastructure, or agentic tooling โ€” I'm happy to chat.