::- every plan is a proof.
portage-ng is a declarative reasoning engine for large scale software configurations, applied to Gentoo Linux. It uses logical reasoning to produce a plan, recording which rule justified each package and under what constraints.
?- written in Prolog.
Why Prolog? It's the artificial-intelligence language built for automated reasoning: programs are logical rules, and running them means deriving proofs. Inference, backtracking and explanation aren't bolted-on machinery — they're the language itself.
?- applied to Gentoo.
Why Gentoo? It's a metadistribution: it distributes knowledge, not binaries — recipes for building every component of a system, and the parameters to tailor each one. A combinatorially vast configuration space — the perfect proving ground for a reasoning engine.
Time to a merge plan across 16,313 packages — identical tree, VDB and configuration; build outcomes from the latest 19,384-package full-tree run. See the numbers ↓
?- why.
Assembling thousands of interdependent components with user-specified feature selections is configuration management, not logistics — it calls for reasoning, not graph traversal. The result: no graph-traversal errors to decipher, but actionable plans where others fail.
Every plan is a formal derivation you can inspect and replay. Each package carries its justification — which rule selected it, which constraints were imposed, which assumptions were made.
Every step can be explained.
When no valid plan exists outright, portage-ng keeps reasoning: it untangles complex USE and keyword conflicts itself and proves a plan under explicit assumptions — exact package.use, package.accept_keywords and package.unmask entries, embedded in the plan, ready to execute.
No trial and error.
Wave planning projects maximal parallelism straight from the availability proofs. Downloads, installs and runtime checks are independently scheduled actions — packages install while others are still downloading.
Faster upgrades.
?- features.
Every card links to a plain-language explanation with examples — or see the complete feature list for everything.
Everything in the pipeline is a single self-describing term: repository, exact version and phase form the spine, and the context — a list of feature terms — carries the metadata: which USE flags the package must be built with, what it must come after. When dependency paths meet, contexts merge by feature unification, a logic programming paradigm.
How it works →
Version domains narrow incrementally across retries; conflicts are recorded as no-goods — closer to CDCL than to traditional backtracking. Feature-term unification merges constraints when packages appear from multiple paths.
How it works →
Strict proving first; if it fails, keyword acceptance, blocker resolution and unmasking are tried in tiers. Each tier is a weaker proof that still carries formal guarantees — every relaxation tracked and reported.
How it works →
The engine reasons about the configuration space rather than committing to a single path. --variants compares how toggling a USE flag or switching a slot changes the proven result.
How it works →
Undeclared build dependencies discovered at build time are diagnosed, learned as durable knowledge, and the plan is re-derived — never patched in flight. Each discovery doubles as an upstream bug report draft.
How it works →
Transient failures pass through signature-keyed retry chains; packaging exceptions are repaired in-transaction. Sub-slot ABI rebuilds, GHC and OCaml breakage detection included — every recovery logged, never silent.
How it works →
Natural-language package search via vector embeddings. --explain turns a plan into prose, --diagnose runs meta-circular self-repair on failed builds; Ollama, Claude, ChatGPT, Gemini and Grok backends query the knowledge base through sandboxed Pengines.
How it works →
Client / server / worker architecture over a Pengine-based HTTPS API with job queues. mDNS/Bonjour discovery forms clusters automatically; mutual TLS secures every connection.
How it works →
Building delegates to Portage's own ebuild infrastructure — the full ecosystem of ebuilds, eclasses and phase functions works unchanged. Emerge-compatible flags, pre-upgrade snapshots, rollback.
How it works →
The prover and planning laws operate on abstract literals; all Gentoo-specific logic lives in a pluggable rules layer behind a rule/2 hook. The same engine can reason about any rule-encoded domain.
How it works →
?- show.
Proven plans in the terminal, interactive graphs and Gantt charts in the browser — and the builder executing a plan, phase by phase.
~ ❯ portage-ng --pretend neovim >>> Emerging : portage://app-editors/neovim-0.11.6-r2:run?{[]} Calculating dependencies... done! └─step 1─┤ download portage://dev-python/tree-sitter-0.25.2 │ └─ file ─┤ 170.27 Kb tree-sitter-0.25.2.tar.gz │ download portage://dev-lua/luv-1.50.0.1 │ download portage://dev-libs/unibilium-2.1.2 ⋮ 16 more downloads └─step 2─┤ install portage://dev-lang/lua-5.1.5-r200 (blocked: soft by dev-lang/lua) │ └─ conf ─┤ USE = "readline deprecated" │ install portage://dev-libs/tree-sitter-c-0.24.1 │ └─ conf ─┤ USE = "python -debug -test" │ │ PYTHON_TARGETS = "python3_13 -python3_12 -python3_14" │ install portage://dev-lua/mpack-1.0.13 │ └─ conf ─┤ LUA_TARGETS = "lua5-1 -lua5-3 -lua5-4 -luajit" ... └─step 7─┤ install portage://app-editors/neovim-0.11.6-r2 │ └─ conf ─┤ USE = "nvimpager -test" │ │ LUA_SINGLE_TARGET = "lua5-1 -luajit" └─step 8─┤ run portage://app-editors/neovim-0.11.6-r2 Total: 57 actions (19 downloads, 19 installs, 19 runs), grouped into 8 steps. 16.85 Mb already downloaded — nothing left to fetch. >>> Blockers added during proving & planning: [blocks B] !dev-lang/lua:0 — soft, phase: install, required by lua-5.1.5-r200 ~ ❯
~ ❯ portage-ng --build app-editors/neovim >>> Emerging : portage://app-editors/neovim-0.11.6-r2:run?{[]} Calculating dependencies... done! └─step 1─┤ download portage://dev-python/tree-sitter-0.25.2 ✓ │ └─ file ─┤ 170.27 Kb tree-sitter-0.25.2.tar.gz ✓ │ download portage://dev-lua/luv-1.50.0.1 ✓ │ └─ file ─┤ 138.72 Kb luv-1.50.0.1.tar.gz ✓ ⋮ 17 more downloads └─step 2─┤ install portage://dev-lang/lua-5.1.5-r200 ✓ │ └─ exec ─┤ ACTION = clean → setup → unpack → prepare → configure → compile → install → merge ✓ │ │ LOG = dev-lang--lua-5.1.5-r200.log │ └─ conf ─┤ USE = "readline deprecated" ... └─step 7─┤ install portage://app-editors/neovim-0.11.6-r2 ⣷ │ └─ exec ─┤ ACTION = clean → setup → unpack → prepare → configure → compile → install → merge (63%) 6/8 │ │ LOG = app-editors--neovim-0.11.6-r2.log │ └─ conf ─┤ USE = "nvimpager -test" │ │ LUA_SINGLE_TARGET = "lua5-1 -luajit" └─step 8─┤ run portage://app-editors/neovim-0.11.6-r2
~ ❯ portage-ng --search name=portage category=sys-apps portage://sys-apps/portage-9999 portage://sys-apps/portage-3.0.77-r3 portage://sys-apps/portage-3.0.77-r1 portage://sys-apps/portage-3.0.77 portage://sys-apps/portage-3.0.72-r1 pkg://sys-apps/portage-9999 ~ ❯ portage-ng --search text editor with syntax highlighting Semantic search: "text editor with syntax highlighting" 84.7% app-editors/micro Modern and intuitive terminal-based text editor 82.3% app-editors/neovim Vim-fork focused on extensibility and usability 79.1% app-editors/vim Vim, an improved vi-style text editor ~ ❯
~ ❯ portage-ng --search-bugs neovim ### Bug search Searching for "neovim" on https://bugs.gentoo.org... Found 6 bugs for "neovim": #586882 [CONFIRMED ] vim-plugin.eclass should allow for neovim as alternate DEPENDS instead of just vim/gvim https://bugs.gentoo.org/show_bug.cgi?id=586882 #612644 [UNCONFIRMED] app-vim/* Add support for neovim https://bugs.gentoo.org/show_bug.cgi?id=612644 #932671 [CONFIRMED ] app-editors/neovim-0.10.0: unresolved soname dependency on /usr/lib64/lua/5.1/lpeg.so https://bugs.gentoo.org/show_bug.cgi?id=932671 #935641 [CONFIRMED ] tree-sitter-grammar.eclass: installs neovim libraries as broken symlinks https://bugs.gentoo.org/show_bug.cgi?id=935641 #965872 [CONFIRMED ] app-editors/neovim-0.11.5 - QA Notice: Unresolved soname dependencies: https://bugs.gentoo.org/show_bug.cgi?id=965872 #967125 [UNCONFIRMED] revdep-rebuild (Python version) falsely reports app-editors/neovim as broken while legacy script does not https://bugs.gentoo.org/show_bug.cgi?id=967125 ~ ❯
~ ❯ portage-ng --upstream neovim ### Upstream version check Checking 1 package against Repology... app-editors/neovim-0.11.6-r2 — upstream: 0.11.6 (legacy) ~ ❯
~ ❯ portage-ng --pretend --explain app-editors/neovim >>> Emerging : portage://app-editors/neovim-0.11.6-r2:run?{[]} Calculating dependencies... done! Total: 57 actions (19 downloads, 19 installs, 19 runs), grouped into 8 steps. Plan explainer ready. Ask questions about the build plan. Type "quit" or "exit" to leave. explain> why is dev-lang/lua in the plan? app-editors/neovim is built with LUA_SINGLE_TARGET=lua5-1, so its Lua modules — dev-lua/luv and dev-lua/mpack — are compiled against Lua 5.1. Both depend on dev-lang/lua-5.1.5-r200, which is why the plan installs it in step 2, before anything that links against it. It carries a soft blocker against the unslotted dev-lang/lua:0; the plan orders the replacement so the conflict never materialises. explain> why is tree-sitter-c in the plan? neovim uses tree-sitter for its built-in syntax highlighting and ships each grammar as a separate package. dev-libs/tree-sitter-c-0.24.1 is a runtime dependency of app-editors/neovim, so it is installed in step 2 — built with USE="python" and PYTHON_TARGETS="python3_13" for the grammar tooling. explain>
~ ❯ portage-ng --llm claude Chat session with claude (claude-sonnet-4-6). Type "quit" or "exit" to leave. claude> how many ebuilds are in the portage tree? I'll query the knowledge base directly rather than guess: <call:swi_prolog> :- aggregate_all(count, cache:ordered_entry(portage, _, _, _, _), N), format('The portage tree contains ~D ebuilds.~n', [N]). </call:swi_prolog> --- computer ------------------------------------------------------------------ The portage tree contains 32,147 ebuilds. ------------------------------------------------------------------------------- The knowledge base holds 32,147 ebuilds — every version of every package in the configured repository, loaded in memory as indexed Prolog facts. That count comes from executing the query against the running system, not from my training data. claude>
?- benchmark.
The benchmark is a level playing field: four engines, same Portage tree snapshot (~32,000 ebuilds), same VDB, same /etc/portage — and the clock measures what you actually feel, the wall time of a cold --pretend from keypress to merge plan. Running as a warm daemon with an ultralight C++ IPC client, portage-ng answers in 55 ms at the median — 22× faster than emerge — and sweeps all 16,313 packages in 25 minutes to emerge's 6 hours. But the metric that really counts is time to a working plan: where emerge hands a conflict back for manual package.use and keyword edits, portage-ng derives those changes itself. On the latest full-tree build run (19,384 packages), that meant building everything emerge could — plus 3,523 packages more, 18.2% of the tree.
Cold --pretend | Median | Total | vs emerge |
|---|---|---|---|
| emerge | 1,234 ms | 6.09 hours | 1.0× |
| cave (Paludis) | 520 ms | 6.22 hours | 2.4× |
| pmerge (pkgcore) | 245 ms | 1.28 hours | 5.0× |
| portage-ng — SWI IPC client | 82 ms | 32.4 minutes | 15.1× |
| portage-ng — C++ IPC client | 55 ms | 25.0 minutes | 22.4× |
Wall clock per package across the 16,313 packages where emerge -vp produced a plan; portage-ng timed as warm daemon plus IPC client — connect, prove, print. Speedups are median ratios — 22.4× = 1,234 ms ÷ 55 ms; cave's total is inflated by 120 s timeouts. From the v1.5.0 pm-bench run — methodology and all-package tables in Chapter 26 — Performance.
All 32,000 ebuilds live in memory as indexed Prolog facts — loaded once in a resident daemon, queried in microseconds; a thin IPC client connects in milliseconds. Portage re-parses metadata on every invocation.
No separate graph-construction phase, no conflict checking afterwards. Over 99% of packages prove without a single retry.
On conflict, Portage discards the whole graph and starts over. portage-ng retries only the affected subtree, armed with learned constraints.
The ordering pass identifies parallel build waves automatically — parallelism is a property of the plan, not an afterthought.
?- architecture.
Each stage is independently testable and replaceable. The resolver and orderer are thin wrappers around one generic prover: pass one derives what to build, pass two derives when.
A runtime object system for Prolog — contexts, classes, instances, inheritance and access control — used internally and available as a general-purpose paradigm. Chapter 21 →
Build-time discoveries (missing providers, wrong USE sets) become durable learned knowledge, and the plan is re-derived — the plan stays prove_plan(Goals, KB), always. Chapter 16 →
?- quickstart.
Installation guide — prerequisites, building, first run
portage-ng(1) manpage — the full command reference
CLI chapter — emerge-compatible flags, resolver hints, output styles
# clone and build $ git clone https://github.com/pvdabeel/portage-ng.git $ cd portage-ng && make build && make install # pretend (dry-run) a build plan $ portage-ng --mode standalone --pretend app-editors/neovim # interactive Prolog shell $ portage-ng --mode standalone --shell # sync the Portage tree $ portage-ng --mode standalone --sync
?- handbook.
Ease of understanding has been a design goal from the start: as a declarative program, portage-ng specifies what must hold rather than how to compute it — its inner workings are meant to be read. Twenty-eight chapters, from installation to contextual logic programming. Self-aware portage-ng, capable of explaining its reasoning to the user in human language.
What you'll learn
Introduction, installation and quick start, configuration
Pipeline, prover, assumptions, version domains, rules, planning, output
CLI, building, LLM integration, distributed proving, upstream bugs, GLSA
Contextual logic programming, context terms, resolver comparison, ordering
Testing and regression, performance and profiling, contributing