A fork can begin with one necessary change and end with a second software project nobody planned to maintain. A package can begin as a convenient dependency and become difficult to replace. A rewrite can promise control while discarding years of edge-case knowledge.
The choice is about ownership: which behavior the team must control, which obligations it can share upstream, and which work it must accept itself. There is no universal winner.
This chapter of The AI Software Factory, in the SalarsNet AI section, proposes a way to decide among a package, a wrapper, a contribution, a maintained fork, and a focused new implementation. No comparative performance or maintenance experiment has been executed for the choices described here.
Name the behavior that creates the decision
Start with a task contract and the particular mismatch. “We need more control” is incomplete until it identifies what the current component cannot do.
For a hypothetical file parser, the mismatch could be that identifiers with leading zeros are converted into numbers. Another could be that the component’s error model cannot distinguish an empty input from a failed parse. A third could concern unsupported deployment conditions.
Each mismatch suggests a different response. Configuration may preserve identifiers. A wrapper may validate results. A contribution may improve the error model. A fork may be justified if the team needs behavior upstream will not support. A rewrite may be appropriate if the required mechanism is small and the existing assumptions are incompatible.
The reuse-before-build chapter establishes the search and contract. This chapter begins after the team knows which difference matters.
Avoid making the ownership choice before inspecting the mechanism. Otherwise a preference for forks or custom code can determine the architecture before the task supplies a reason.
A package keeps an upstream relationship
Using a published package lets the team consume a versioned artifact while its original maintainers continue developing the project. The team still owns integration, configuration, tests, and its customer promise.
The arrangement works well when the package fits the contract and changes can be reviewed through an ordinary update process. It becomes less comfortable when the product depends on undocumented behavior or modifications the package does not support.
Pin and record the evaluated state appropriately, then define how updates will be reviewed. Staying on one version forever can avoid immediate compatibility work while accumulating security or support problems.
A package’s maintainers do not become the product’s support team automatically. If a customer needs a quick response, the business must diagnose and handle the issue within its own promise.
The component-evaluation chapter examines those obligations. Package adoption shares implementation maintenance; it does not transfer responsibility for the product’s outcome.
A wrapper can preserve a narrow boundary
A wrapper adapts an external mechanism to your task contract. It can normalize inputs, validate outputs, limit authority, and translate errors into a form the product understands.
For the hypothetical parser, a wrapper might check required columns, preserve a source reference, and reject output when an identifier changes meaning. It might not need to modify the parser itself.
The wrapper should remain small enough to explain. If it contains extensive workarounds for the dependency’s assumptions, it can become a hidden rewrite with two systems to maintain.
Preserve tests of both the wrapper’s contract and the dependency behavior that matters. An upstream update can change assumptions even when the public API remains the same.
A wrapper can improve replacement options, but it does not eliminate them. A new parser may have a different data model or failure behavior. The boundary helps make the differences visible and keeps the product’s promise independent of one package name.
Contribute when the change fits the project
An upstream contribution can reduce long-term divergence when the required behavior belongs in the project’s supported scope. Begin by reading contribution guidance and relevant discussions.
Describe the problem and evidence before proposing a large change. A maintainer may know a configuration or constraint the adopter missed. A minimal reproducible case can clarify the issue without importing private customer data.
Respect the project’s decision process. Maintainers may reject a change because it conflicts with scope, adds support burden, or needs more design. Their decision does not prove the request lacks value; it identifies a boundary between their project and your product.
Do not promise the customer that an upstream change will ship on your timeline unless the arrangement supports that promise. A contribution can be valuable while the product still needs a temporary supported alternative.
Record contribution terms and provenance. Licensing obligations and any contributor agreement require examination appropriate to the actual project. The commercial licensing chapter addresses the rights side of reuse.
Fork deliberately when divergence is necessary
A fork provides a separate development line related to the original project. GitHub’s template documentation distinguishes forks, which retain parent commit history, from template-created repositories, which begin with a new initial commit. The distinction matters for understanding origin and ongoing divergence. Creating a repository from a template.
A maintained fork can be appropriate when the team needs a change that upstream will not accept or cannot provide within the product’s constraints. The decision should include an owner, update strategy, tests, and a limit on divergence.
Do not fork simply because editing the source feels easier than understanding the API. A small configuration or adapter can avoid taking responsibility for the entire project.
The fork’s modifications should remain identifiable. Preserve the upstream reference and explain why each material change exists. That record helps future maintainers decide whether a change remains necessary after upstream evolves.
A fork is a product commitment. If the team cannot allocate maintenance capacity, its apparent short-term speed can create a future support problem.
Estimate the cost of divergence
The initial change is only one part of fork cost. The team must monitor upstream fixes, decide which apply, resolve conflicts, test the combined state, and release its own artifact.
A fork that changes a central data model can make later merges difficult. A narrow patch isolated behind a stable interface may be easier to preserve. The actual code relationship matters more than the number of changed lines alone.
For a hypothetical parser fork, changing type inference could affect every input path. Adding a specific opt-in preservation mode could be narrower. The team should inspect the consequences rather than assume a small patch is a small obligation.
Track divergence over time. Are patches shrinking because upstream adopts them, staying stable, or expanding as each customer adds a special case? The pattern can signal whether the fork remains a sensible boundary.
Set a reconsideration trigger. A fork may be retired when upstream supplies the required behavior, when maintenance exceeds capacity, or when the product contract changes enough that another mechanism fits better.
Identify the behavior a rewrite must replace
A new implementation can fit a narrow contract cleanly. It can also rediscover failures the existing project already handles.
Before rewriting, identify the mechanism the team truly needs. A small transformation may be feasible to own. A general parser, authentication system, or synchronization engine can contain extensive edge cases and security concerns.
List the behavior that would be lost by leaving the current component. Include difficult inputs, compatibility, error handling, and operating tools. A rewrite comparison that counts only the happy path is incomplete.
Use the existing mechanism as a baseline when appropriate. Compare outputs under independently specified cases, including exceptions. Keep protected later cases outside development if the team intends to claim transfer.
No rewrite comparison is executed here. A claim that custom code is faster, safer, or cheaper would need actual artifacts and results under a suitable scope.
Compare obligations under the same contract
The options should be evaluated against the same required outcome. A package plus a wrapper, a fork, and a rewrite may differ in who supplies behavior, but the customer’s task remains the benchmark.
Record initial work, ongoing updates, rights obligations, operating support, and exit costs. Use ranges where estimates are uncertain. Identify which unknown could reverse the decision.
An illustrative comparison could show a package requiring a small adapter, a fork requiring recurring merge review, and a rewrite requiring more initial tests. It should not assign invented universal effort ratios. The actual project and team determine those costs.
Required correctness and authority boundaries come before convenience. A cheap option that cannot preserve the task’s essential meaning is not eligible merely because it scores well on development speed.
If two options remain close, run the cheapest informative fit check. A harmless difficult input can resolve an important question more effectively than a long debate about architecture preference.
Preserve data and migration options
Ownership decisions affect customer records as well as code. A component-specific storage format can make replacement harder even when the calling interface is narrow.
Keep the product’s essential data meaning explicit. Preserve source identifiers, history needed for disputes, and a supported export path appropriate to the task. Do not assume that exporting raw bytes establishes a usable migration.
A fork or rewrite may require converting stored state. Test that conversion with representative harmless records and define how errors will be handled. A code rollback cannot automatically reverse every data change.
Separate migration preparation from promotion. The team should know which artifact and state will be used before committing the change. A customer-facing transition needs a clear account of interruptions or review work where relevant.
The rollback chapter examines release recovery. Ownership choice should make recovery possible rather than leaving it as an afterthought.
Avoid the accidental fork
An accidental fork occurs when the team modifies a dependency locally without treating the modification as a maintained artifact. The change may disappear during reinstall or be unknown to other developers.
Preserve any required modification in a reproducible source and build process. Record its relation to upstream, exact reason, owner, and tests. Another environment should not silently receive the unmodified package while production relies on a local patch.
The same issue can arise with copied code. A function copied into several apps may evolve independently without a clear update path. The team has accepted ownership even if it never uses the word fork.
A small project does not need elaborate tooling to make this visible. A versioned patch and concise record can be enough if the build and review process enforce it.
The important condition is that the installed behavior can be explained from the committed source and dependency record. Hidden local changes undermine review and release confidence.
Plan the transition before changing ownership
Moving from a package to a fork or rewrite is a release change. The team needs a tested artifact, a comparison with current behavior, and a way to identify which implementation serves each job.
Begin with harmless shadow comparison where the task permits it. The new mechanism can produce an output for inspection while the existing mechanism remains authoritative. A read-only transformation is easier to compare this way than an external action such as publishing or charging. Do not run duplicate consequential actions merely to compare systems.
Define how disagreements will be resolved. If the new converter and current converter return different identifiers, a reviewer needs the original input and the task’s rule, not a vote between implementations. The current mechanism can be wrong too. The independent contract decides the expected result.
Record performance only when measured under comparable conditions. A faster local example does not establish lower production cost or better reliability. Include review work, failure handling, and important resource constraints if those outcomes affect the ownership choice.
Promotion should occur through the existing authorized release process. Preserve the predecessor artifact and understand what state changes make rollback difficult. If the new mechanism changes stored data, test the migration and recovery separately from the code switch.
Give the next maintainer a concrete handoff
An ownership choice becomes real when another person can operate it. A fork handoff should identify upstream origin, maintained patches, build instructions, contract cases, update process, owner, and known limitations. A rewrite handoff should identify the mechanism and behaviors deliberately omitted from the earlier component.
Explain the decisions that are easy to misunderstand. Perhaps the fork preserves a compatibility behavior that looks obsolete. Perhaps the wrapper rejects an input the underlying package accepts because the product cannot safely interpret it. Perhaps the rewrite supports a narrow format intentionally.
Keep that explanation near the code or component record. A future agent or developer may otherwise remove the unusual behavior as cleanup and reintroduce the original problem.
The handoff should also state how to reproduce a failure without exposing private customer records. Harmless representative cases can preserve important conditions and let maintainers investigate changes responsibly.
A team does not need a large maintenance manual for every small function. It needs enough evidence and explanation that ownership survives the creator’s absence. If the mechanism cannot be handed off within the team’s capacity, that difficulty belongs in the original decision.
Revisit the decision after evidence changes
The preferred option can change. Upstream may add the required feature. A vulnerability may affect the current version. The product may narrow its contract. A new customer may reveal a previously unknown assumption.
Treat those events as review triggers rather than defending the original choice. Preserve the earlier reasoning so the team can see which fact changed.
A package can become a fork, and a fork can return to a package. A custom implementation can be replaced by a mature external mechanism. These changes are easier when the task contract and tests remain separate from the implementation.
Do not switch merely because a new tool is fashionable. The replacement should improve a consequential condition: correctness, support, maintainability, authority, cost, or a required capability.
A useful review asks whether the current arrangement still gives the team a manageable obligation that delivers the customer result. That question remains stable as the tools change.
What Would We Do at Salars?
A proposed Salars ownership decision would start with one documented mismatch in a component serving a specific app. The team would check configuration and a narrow wrapper before choosing a fork or rewrite.
If the change belonged upstream, the team could prepare a transparent contribution within the project’s rules. If a fork were necessary, it would have a named owner, identifiable patches, an update plan, and a reconsideration trigger. Local unrecorded modifications would not become production behavior.
A rewrite would require a narrow contract and independent fit cases, including difficult inputs and failure conditions. Migration and rollback would be considered before promotion.
This proposal asserts no completed fork, contribution, or performance improvement. The outcome would be an ownership decision whose maintenance and data obligations the team can explain.
The right choice is the one that preserves the required behavior while leaving the team able to understand, update, support, and eventually replace it.
Sources
- GitHub: Creating a repository from a template, official distinction between template creation and fork history.
- GitHub: Licensing a repository, rights context for reuse and modification.
Sources inspected October 7, 2026. Ownership framework, parser cases, and Salars actions are proposed or hypothetical. No fork, rewrite benchmark, or contribution was executed for this article.
Loading comments…