From Fork to Framework

From Fork to Framework, Part 1: Standing on Shoulders

We started AOCodex and AOSentry by forking two excellent open-source projects. Here is what we learned about the limits of that approach.

Abstract illustration of intertwined code helixes diverging

The Fork-Then-Diverge Pattern

Every engineering team that has launched a product on top of an open-source fork knows the feeling: the first two weeks are intoxicating. You inherit years of upstream work, a working CI pipeline, a contributor community that has already filed the bugs you would have discovered the hard way. You feel like you have cheated the calendar.

Then the merge conflicts arrive.

This post is an honest account of what happened when we built the first generation of AO Cyber Systems on two open-source forks – and why we eventually abandoned both. If you are evaluating a fork-based strategy for your own product, we hope the timeline, the architecture, and the lessons save you some pain.

The Two Forks

On October 2, 2025, we created two repositories:

  • AOCodex, forked from LibreChat by Danny Avila. LibreChat is a TypeScript/React/Node.js chat application with over 3,200 upstream commits at the time of our fork. It gave us a polished multi-model chat UI, conversation management, plugin architecture, and a MongoDB persistence layer – essentially, a production-ready ChatGPT alternative we could rebrand and extend.

  • AOSentry, forked from LiteLLM by BerriAI. LiteLLM is a Python proxy that presents a unified OpenAI-compatible API across dozens of model providers. With over 26,000 upstream commits, it is one of the most actively developed projects in the LLM infrastructure space. It gave us provider abstraction, token tracking, rate limiting, and a Next.js admin dashboard.

Together, these two forks formed the backbone of our v1 architecture.

The V1 Architecture

graph TB subgraph AOCodex FE["React Frontend, TypeScript + Recoil/Jotai"] API["Node.js / Express API"] MONGO[(MongoDB)] FE --> API API --> MONGO end subgraph AOSentry PROXY["Python FastAPI Proxy, Unified AI Provider Interface"] PRISMA["Prisma ORM"] PG[(PostgreSQL)] ADMIN["Next.js Admin UI"] CELERY["Celery Workers"] PROXY --> PRISMA PRISMA --> PG ADMIN --> PROXY CELERY --> PROXY end subgraph SharedInfra REDIS[(Redis)] end API -->|AI requests| PROXY PROXY --> REDIS CELERY --> REDIS PROXY -->|OpenAI, Anthropic, Azure, Bedrock| PROVIDERS["AI Providers"] style FE fill:#1a1a2e,stroke:#d4a853,color:#fff style API fill:#1a1a2e,stroke:#d4a853,color:#fff style MONGO fill:#1a1a2e,stroke:#d4a853,color:#fff style PROXY fill:#1a1a2e,stroke:#d4a853,color:#fff style PRISMA fill:#1a1a2e,stroke:#d4a853,color:#fff style PG fill:#1a1a2e,stroke:#d4a853,color:#fff style ADMIN fill:#1a1a2e,stroke:#d4a853,color:#fff style CELERY fill:#1a1a2e,stroke:#d4a853,color:#fff style REDIS fill:#1a1a2e,stroke:#d4a853,color:#fff style PROVIDERS fill:#1a1a2e,stroke:#d4a853,color:#fff

The architecture split responsibilities cleanly in theory: AOCodex owned the user experience, conversation state, and authentication; AOSentry owned model routing, cost tracking, and provider failover. In practice, the boundary was messier than it looked on a whiteboard. Both projects carried their own admin UIs, their own auth systems, and their own opinions about how to talk to a database.

The Timeline

What follows is the condensed story of 10 weeks on AOCodex and roughly 19 weeks on AOSentry. The branch names in our git history tell it more honestly than any retrospective could.

Week 1-2 (October 2-15, 2025): The Honeymoon

Everything moved fast. We rebranded both projects, swapped color schemes, configured CI, and deployed to staging. AOCodex inherited LibreChat’s Docker Compose setup, so we had a working multi-container environment on day one. AOSentry’s proxy layer worked immediately with our existing OpenAI and Anthropic API keys.

The first custom features landed: Stripe billing integration on AOCodex, PII detection using Microsoft Presidio on AOSentry, and a shared guardrails pipeline that could intercept prompts before they reached a model provider.

Week 3-5 (October 16 - November 5, 2025): Divergence Begins

We were committing heavily – AOCodex would accumulate 522 custom commits by December 12. Features included admin dashboards, Docker Swarm deployment manifests, and multi-architecture container builds for ARM and x86. Each feature pulled us further from upstream.

The Recoil-to-Jotai migration was our first taste of upstream turbulence. LibreChat switched its state management library mid-stream, and because we had modified dozens of components that imported Recoil atoms, the migration touched files across our entire fork. It was a three-day detour that delivered zero user-facing value.

Week 6-7 (November 6-19, 2025): The 759-File Merge

The branch feature/AOC-61-merge-latest-librechat-code tells the story of our attempt to pull in LibreChat v0.8.1-rc1. The merge produced 759 file conflicts. Not 759 lines – 759 files.

Some of these conflicts were trivial: both sides had touched package.json or configuration files. Many were not. Our Stripe billing hooks had been wired into LibreChat’s user model, which upstream had restructured. Our guardrails middleware sat in a request pipeline that upstream had refactored. Our admin dashboard components imported utilities that upstream had moved to different modules.

We created backup-pre-librechat-merge-20251112 before attempting the merge – a branch name that radiates confidence. The merge took five full engineering days to resolve, test, and stabilize. During that time, no product features shipped.

Week 8-10 (November 20 - December 12, 2025): The LangChain Saga

On AOSentry’s side, the LangChain v1 upgrade was the equivalent of the 759-file merge. LangChain’s migration from v0 to v1 changed import paths, renamed core abstractions, and deprecated patterns we had built on top of. Because AOSentry inherited LiteLLM’s LangChain integration, and we had extended it with custom chain logic for our guardrails pipeline, the upgrade cascaded through a significant portion of our codebase.

Meanwhile, the refactor/remove-litellm-branding branch represented our effort to white-label AOSentry. Rebranding a project with 26,000 commits is not a find-and-replace operation. The name appeared in log messages, error strings, API response headers, database migration comments, documentation, Docker image tags, environment variable prefixes, and Prometheus metric names. Every occurrence had to be evaluated: some were internal, some were user-facing, and some were contractual (LiteLLM’s enterprise license, which we needed to remove entirely and replace with our own licensing logic).

December 12, 2025: The Decision

By mid-December, we had 522 custom commits on AOCodex and 188 on AOSentry. We had built real product value: a working Stripe billing system, PII detection, content guardrails, admin dashboards, Docker Swarm orchestration, and multi-arch builds. But we had also spent a disproportionate amount of engineering time on merge conflicts, upstream migrations, and rebranding work that did not move the product forward.

The fork-based approach had another cost that is harder to quantify: architectural ceiling. Both upstream projects made reasonable choices for their use cases that were wrong for ours. LibreChat used MongoDB because document storage is natural for chat conversations – but we needed relational integrity for billing, teams, and access control. LiteLLM used Prisma with PostgreSQL, which was closer to what we wanted, but its schema was optimized for a proxy’s concerns (API keys, spend tracking, rate limits) rather than a product’s concerns (users, organizations, subscriptions, audit logs).

We could not change the database layer of either fork without essentially rewriting them. And if we were going to rewrite them, the fork was no longer saving us time.

What We Built (That Mattered)

It is important to acknowledge what the fork period produced beyond frustration. Several capabilities we prototyped during these ten weeks survived into later iterations:

CapabilityOriginSurvived?
Stripe billing integrationAOCodex customYes – billing logic carried forward
PII detection (Presidio)AOSentry customYes – became a core guardrail
Content guardrails pipelineBoth forksYes – architecture refined in v2
Docker Swarm deploymentAOCodex customNo – replaced by App Platform, then Kubernetes
Multi-arch container buildsAOCodex customYes – still building ARM + x86
Admin dashboardsBoth forksPartially – rebuilt in each subsequent iteration
LiteLLM provider abstractionAOSentry upstreamYes – used as a library in v2, not a fork

The fork period was not wasted. It was a rapid, high-fidelity prototyping phase that taught us what our product needed to be. The mistake would have been staying in that phase longer than necessary.

The Lessons

Forks are prototypes, not foundations

A fork gives you a running start on features, but it also gives you a running start on technical debt. Every upstream merge is a tax. Every upstream architectural decision you disagree with is a constraint. The more you customize, the higher the tax and the tighter the constraints.

Upstream velocity is a double-edged sword

We chose LibreChat and LiteLLM partly because they were active projects with strong communities. That activity meant bug fixes and new features we could inherit – but it also meant the codebase was a moving target. A dormant upstream is easier to fork; an active upstream is more valuable to fork but harder to maintain.

Rebranding is architectural work

Removing another project’s identity from a large codebase is not cosmetic. It touches error handling, logging, metrics, documentation, environment variables, API contracts, and sometimes licensing. Budget for it accordingly, or accept that your product will carry another project’s name in places your customers might see.

The merge conflict is the symptom, not the disease

The 759-file merge was painful, but the real problem was that we had modified 522 files in a codebase we did not control. Every custom commit increased the surface area for future conflicts. There is no tooling that solves this; it is a fundamental tension in the fork model.

Credit Where It Is Due

Danny Avila’s LibreChat and BerriAI’s LiteLLM are excellent projects. LibreChat gave us a mature, well-tested chat interface that would have taken months to build from scratch. LiteLLM’s provider abstraction layer is genuinely best-in-class – we still use it today, as a dependency rather than a fork. Both projects have active communities, responsive maintainers, and clear roadmaps.

Our decision to move away from the forks was not a commentary on their quality. It was a recognition that our product requirements had diverged far enough from their goals that maintaining alignment was costing more than building independently.

What Came Next

The fork experiment ended in mid-December 2025. What replaced it was a clean-room rewrite using FastAPI and SvelteKit – a story with its own victories and its own hard lessons.

That is Part 2.

← Back to Blog