diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -34,7 +34,7 @@
 
 ```bash
 cabal update
-cabal install --overwrite-policy=always phino-0.0.109
+cabal install --overwrite-policy=always phino-0.0.110
 phino --version
 ```
 
@@ -454,55 +454,55 @@
 === parse/phi ===
   warmup:     3 iterations
   batches:    10 x 1
-  total:      1169007.436 μs
-  avg:        116900.744 μs
-  min:        100971.842 μs
-  max:        151797.838 μs
-  std dev:    17291.748 μs
+  total:      1823744.302 μs
+  avg:        182374.430 μs
+  min:        166020.495 μs
+  max:        235134.815 μs
+  std dev:    21915.839 μs
 === parse/xmir ===
   warmup:     3 iterations
   batches:    10 x 1
-  total:      5597441.785 μs
-  avg:        559744.178 μs
-  min:        513882.882 μs
-  max:        594965.205 μs
-  std dev:    25969.225 μs
+  total:      7518652.376 μs
+  avg:        751865.238 μs
+  min:        680879.331 μs
+  max:        834737.644 μs
+  std dev:    43702.346 μs
 === rewrite/normalize ===
   warmup:     3 iterations
   batches:    10 x 1
-  total:      462608.655 μs
-  avg:        46260.866 μs
-  min:        45419.580 μs
-  max:        46969.027 μs
-  std dev:    488.480 μs
+  total:      628087.263 μs
+  avg:        62808.726 μs
+  min:        54676.886 μs
+  max:        71063.411 μs
+  std dev:    5609.304 μs
 === print/sweet/multiline ===
   warmup:     3 iterations
   batches:    10 x 1
-  total:      2470222.565 μs
-  avg:        247022.256 μs
-  min:        237266.284 μs
-  max:        257086.226 μs
-  std dev:    5473.812 μs
+  total:      4237321.401 μs
+  avg:        423732.140 μs
+  min:        399469.667 μs
+  max:        442779.214 μs
+  std dev:    13346.416 μs
 === print/sweet/flat ===
   warmup:     3 iterations
   batches:    10 x 1
-  total:      2445638.241 μs
-  avg:        244563.824 μs
-  min:        229281.911 μs
-  max:        294227.329 μs
-  std dev:    17698.464 μs
+  total:      4107718.132 μs
+  avg:        410771.813 μs
+  min:        395024.009 μs
+  max:        430270.510 μs
+  std dev:    11672.453 μs
 === print/salty/multiline ===
   warmup:     3 iterations
   batches:    10 x 1
-  total:      8935463.749 μs
-  avg:        893546.375 μs
-  min:        857992.909 μs
-  max:        945729.836 μs
-  std dev:    27023.850 μs
+  total:      14208719.443 μs
+  avg:        1420871.944 μs
+  min:        1398296.594 μs
+  max:        1455751.449 μs
+  std dev:    17141.226 μs
 ```
 
 The results were calculated in [this GHA job][benchmark-gha]
-on 2026-08-28 at 21:26,
+on 2026-08-31 at 15:18,
 on Linux with 4 CPUs.
 
 <!-- benchmark_end -->
@@ -551,4 +551,4 @@
 [jna]: https://github.com/java-native-access/jna
 [jna-native]: https://github.com/java-native-access/jna/blob/master/src/com/sun/jna/Native.java
 [jeo]: https://github.com/objectionary/jeo-maven-plugin
-[benchmark-gha]: https://github.com/objectionary/phino/actions/runs/33210677726
+[benchmark-gha]: https://github.com/objectionary/phino/actions/runs/33403281766
diff --git a/phino.cabal b/phino.cabal
--- a/phino.cabal
+++ b/phino.cabal
@@ -1,6 +1,6 @@
 cabal-version: 3.0
 name: phino
-version: 0.0.110
+version: 0.0.111
 license: MIT
 synopsis: Command-Line Manipulator of 𝜑-Calculus Expressions
 description: Please see the README on GitHub at <https://github.com/objectionary/phino#readme>
diff --git a/src/XMIR.hs b/src/XMIR.hs
--- a/src/XMIR.hs
+++ b/src/XMIR.hs
@@ -393,13 +393,12 @@
   | not (hasAttr "name" cur) = throwIO (InvalidXMIRFormat "Formation children must have @name attribute" cur)
   | not (hasAttr "base" cur) = do
       name <- getAttr "name" cur
-      bds <- mapM (`xmirToFormationBinding` (name : fqn)) (cur C.$/ C.element (toName "o")) >>= uniqueBindings'
       case name of
         "λ" -> BiLambda . Function <$> lambdaFunction
         ('α' : _) -> throwIO (InvalidXMIRFormat "Formation child @name can't start with α" cur)
-        "φ" -> pure (BiTau AtPhi (ExFormation (withVoidRho bds)))
-        "ρ" -> pure (BiTau AtRho (ExFormation (withVoidRho bds)))
-        _ -> pure (BiTau (AtLabel (T.pack name)) (ExFormation (withVoidRho bds)))
+        "φ" -> BiTau AtPhi <$> xmirToFormation cur (name : fqn)
+        "ρ" -> BiTau AtRho <$> xmirToFormation cur (name : fqn)
+        _ -> BiTau (AtLabel (T.pack name)) <$> xmirToFormation cur (name : fqn)
   | otherwise = do
       name <- getAttr "name" cur
       base <- getAttr "base" cur
@@ -422,6 +421,18 @@
       | hasText cur = T.strip . T.pack <$> getText cur
       | otherwise = pure (T.pack (intercalate "_" ("L" : reverse fqn)))
 
+-- A formation keeps its Δ data in the text content of its own element, the way
+-- the printer emits a Δ binding, while the rest of the bindings live in the
+-- nested <o> elements
+xmirToFormation :: C.Cursor -> [String] -> IO Expression
+xmirToFormation cur fqn = do
+  nested <- mapM (`xmirToFormationBinding` fqn) (cur C.$/ C.element (toName "o"))
+  bds <- if hasText cur then (: nested) <$> delta else pure nested
+  ExFormation . withVoidRho <$> uniqueBindings' bds
+  where
+    delta :: IO Binding
+    delta = BiDelta . bytesToBts . T.unpack . T.strip . T.pack <$> getText cur
+
 xmirToExpression :: C.Cursor -> [String] -> IO Expression
 xmirToExpression cur fqn
   | hasAttr "base" cur = do
@@ -450,9 +461,7 @@
         'Φ' : '.' : rest -> xmirToExpression' ExRoot "Φ" rest cur fqn
         'ξ' : '.' : rest -> xmirToExpression' ExXi "ξ" rest cur fqn
         _ -> throwIO (InvalidXMIRFormat "The @base attribute must be either ['∅'|'Φ'] or start with ['Φ.'|'ξ.'|'.']" cur)
-  | otherwise = do
-      bds <- mapM (`xmirToFormationBinding` fqn) (cur C.$/ C.element (toName "o")) >>= uniqueBindings'
-      pure (ExFormation (withVoidRho bds))
+  | otherwise = xmirToFormation cur fqn
   where
     xmirToExpression' :: Expression -> String -> String -> C.Cursor -> [String] -> IO Expression
     xmirToExpression' start symbol rst c names =
diff --git a/test/XMIRSpec.hs b/test/XMIRSpec.hs
--- a/test/XMIRSpec.hs
+++ b/test/XMIRSpec.hs
@@ -238,9 +238,21 @@
               (expectationFailure ("Failed xpaths:\n - " ++ intercalate "\n - " failed ++ "\nXMIR is:\n" ++ printXMIR xmir'))
       )
 
-  describe "XMIR round-trip" $
+  describe "XMIR round-trip" $ do
     it "keeps λ function name and bound ρ" $ do
       expr <- parseExpressionThrows "[[ k -> [[ x -> ?, L> Lorg_eolang_number_plus, ^ -> [[ y -> ? ]] ]] ]]"
+      xmir' <- expressionToXMIR expr defaultXmirContext
+      back <- xmirToPhi xmir'
+      back `shouldBe` expr
+
+    it "keeps Δ data bound to a named attribute" $ do
+      expr <- parseExpressionThrows "[[ k -> [[ a -> [[ D> 01-02 ]], ^ -> [[ D> 03-04 ]] ]] ]]"
+      xmir' <- expressionToXMIR expr defaultXmirContext
+      back <- xmirToPhi xmir'
+      back `shouldBe` expr
+
+    it "keeps Δ data in a dispatched formation" $ do
+      expr <- parseExpressionThrows "[[ k -> [[ D> 01-02 ]].plus ]]"
       xmir' <- expressionToXMIR expr defaultXmirContext
       back <- xmirToPhi xmir'
       back `shouldBe` expr
