diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Revision history for fuyu-gpio
+
+## 0.0.9.0 -- 2026-08-13
+
+* Initial release. High-level managed abstraction layer for libgpiod built on top of fuyu-gpio-direct.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,502 @@
+                  GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+                  GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+                            NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/examples/01-blink.hs b/examples/01-blink.hs
new file mode 100644
--- /dev/null
+++ b/examples/01-blink.hs
@@ -0,0 +1,49 @@
+-- In this example we will learn the basic usage of libgpiod line configuration for output.
+-- We check and initialize a GPIO chip, set a line direction to output mode, and blink an LED 10 times
+-- using nested resource allocation brackets ('withChip', 'withSettings', 'withConfig', 'withRequest').
+module Main where
+
+-- High-level resource brackets & utility functions
+import Fuyu.GPIO.Chip (withChip, isGPIOChip)
+import Fuyu.GPIO.Line (withSettings, withConfig, withRequest)
+import qualified Fuyu.GPIO.Line as Line
+
+-- Base & third-party libraries
+import Control.Concurrent (threadDelay)
+import Control.Monad (replicateM_)
+import Data.Vector.Storable (singleton)
+
+-- We could check if this file is a GPIO Chip with 'isGPIOChip' function
+chipPath :: FilePath
+chipPath = "/dev/gpiochip0" 
+
+-- In Orange Pi devices we could find this information with 'gpio readall' command (or in docs)
+-- In this case the line offset 269 corresponds to physical pin 7 
+ledOffset :: Line.Offset 
+ledOffset = Line.Offset 269 
+
+main :: IO ()
+main = do
+   isChip <- isGPIOChip chipPath
+   if isChip
+     then do 
+       putStrLn "Example started: LED blinking"
+       runApp
+     else putStrLn $ chipPath ++ " does not correspond to a valid GPIO Chip"
+
+runApp :: IO ()
+runApp = do
+   withChip chipPath $ \chip -> do
+      withSettings $ \settings -> do
+         Line.setDirection settings Line.DirOutput
+         withConfig $ \config -> do
+            Line.addSettings config (singleton ledOffset) settings
+            -- The use of Nothing instead of a 'RequestConfig' means that we are using a NULL request configuration object.
+            -- (Do not confuse Fuyu.GPIO.RequestConfig with Fuyu.GPIO.Line.Config, the first one is used for kernel options
+            -- and the second one is used for line config).  
+            withRequest chip Nothing config $ \request -> do
+               replicateM_ 10 $ do 
+                 Line.setValue request ledOffset Line.Active
+                 threadDelay 500000 -- 0.5 seconds pause   
+                 Line.setValue request ledOffset Line.Inactive
+                 threadDelay 500000 -- 0.5 seconds pause
diff --git a/examples/02-button.hs b/examples/02-button.hs
new file mode 100644
--- /dev/null
+++ b/examples/02-button.hs
@@ -0,0 +1,66 @@
+-- In this example we will learn how to handle a GPIO input line with edge event detection.
+-- We configure internal pull-up bias, line debounce filtering, and edge detection (Rising & Falling),
+-- using a user-space event buffer with 'userBufferCapacity 1' to read button press events.
+module Main where
+
+-- High-level resource brackets & exception handling
+import Fuyu.GPIO.Chip (withChip)
+import Fuyu.GPIO.Exception (withGpioApp)
+import Fuyu.GPIO.Line (withSettings, withConfig, withRequest)
+import qualified Fuyu.GPIO.Line as Line
+import Fuyu.GPIO.EdgeEvent (withBuffer)
+import qualified Fuyu.GPIO.EdgeEvent as Event
+
+-- Base & third-party libraries
+import Control.Monad (forever)
+import Data.Vector.Storable (singleton)
+
+chipPath :: FilePath
+chipPath = "/dev/gpiochip0" 
+
+-- This constant defines the maximum duration 'waitEvents' will wait for an event.
+-- (This timeout can also be configured as infinite or immediate).
+fiveSecondsNs :: Event.Timeout
+fiveSecondsNs = Event.Nanoseconds 5000000000 
+
+-- Do not confuse this with kernel ring buffer capacity. 'Capacity' refers to the user-space event buffer.
+-- It is clamped between 1 and 1024, and must be constructed via 'userBufferCapacity'
+-- (passing 0 defaults to 64).
+bufferCapacity :: Event.Capacity
+bufferCapacity = Event.userBufferCapacity 1 
+ 
+buttonOffset :: Line.Offset
+buttonOffset = Line.Offset 257 
+
+
+buttonSettings :: Line.Settings -> IO ()
+buttonSettings stgs = do
+  Line.setDirection stgs Line.DirInput     -- Configure line as input mode
+  Line.setBias stgs Line.BiasPullUp        -- Enable internal pull-up resistor
+                                           -- (the physical button connects GND when pressed, driving the line to Inactive)
+  Line.setDebouncePeriodUs stgs 20000      -- 20ms debounce period to filter out mechanical contact bounce without threadDelay
+  Line.setEdgeDetection stgs Line.EdgeBoth -- Listen for both Rising and Falling edge transitions
+
+buttonWorker :: Line.Request -> Event.Buffer -> IO ()
+buttonWorker req buf = do
+  res <- Event.waitEvents req fiveSecondsNs
+  case res of 
+    Event.EventReady readyReq -> do
+      events <- Event.readEvents readyReq buf -- Read events from user buffer (configured with capacity 1)
+      print events    
+    Event.TimeoutResult -> putStrLn "Timeout: No event was read" -- Printed after the 5-second wait timeout expires
+
+main :: IO ()
+main = withGpioApp runApp
+
+runApp :: IO ()
+runApp = do
+  withChip chipPath $ \chip -> do
+    withSettings $ \settings -> do
+      buttonSettings settings
+      withConfig $ \config -> do
+        Line.addSettings config (singleton buttonOffset) settings
+        withRequest chip Nothing config $ \request -> do
+          withBuffer bufferCapacity $ \buffer -> do
+            putStrLn "Loop started: Press the button to generate events or Ctrl+C to exit"
+            forever (buttonWorker request buffer)
diff --git a/examples/03-led-and-button.hs b/examples/03-led-and-button.hs
new file mode 100644
--- /dev/null
+++ b/examples/03-led-and-button.hs
@@ -0,0 +1,112 @@
+-- In this example we will learn how to coordinate GPIO output (LED blinking) and input (button press)
+-- concurrently. We launch two worker threads with 'forkIO' and use an 'MVar' to dynamically
+-- control the LED blinking speed upon detecting button press edge events.
+module Main where
+
+-- High-level resource brackets & exception handling
+import Fuyu.GPIO.Chip (withChip)
+import Fuyu.GPIO.Exception (withGpioApp)
+import Fuyu.GPIO.Line (withSettings, withConfig, withRequest)
+import qualified Fuyu.GPIO.Line as Line
+import Fuyu.GPIO.EdgeEvent (withBuffer)
+import qualified Fuyu.GPIO.EdgeEvent as Event
+
+-- Base & third-party libraries
+import Control.Concurrent (MVar, forkIO, killThread, modifyMVar_, newMVar, readMVar, threadDelay)
+import Control.Exception (finally)
+import Control.Monad (forever)
+import Data.Vector.Storable (singleton)
+import System.IO (BufferMode(NoBuffering), hSetBuffering, stdout)
+
+chipPath :: FilePath
+chipPath = "/dev/gpiochip0"
+
+-- This constant defines the maximum duration 'waitEvents' will wait for an event.
+-- A short 100ms timeout yields execution back to the RTS so worker threads run smoothly.
+waitTimeoutNs :: Event.Timeout
+waitTimeoutNs = Event.Nanoseconds 100000000 
+
+-- Do not confuse this with kernel ring buffer capacity. 'Capacity' refers to the user-space event buffer.
+-- It is clamped between 1 and 1024, and must be constructed via 'userBufferCapacity'
+-- (passing 0 defaults to 64).
+bufferCapacity :: Event.Capacity
+bufferCapacity = Event.userBufferCapacity 1
+
+ledOffset :: Line.Offset
+ledOffset = Line.Offset 256 
+
+buttonOffset :: Line.Offset
+buttonOffset = Line.Offset 271
+
+type Microseconds = Int
+
+-- Available blinking speed states
+data LooptimeState = OneSec | HalfSec | FifthOfSec | TenthOfSec
+  deriving (Eq, Show)
+
+-- Convert LooptimeState into delay duration in microseconds
+stateToMicroseconds :: LooptimeState -> Microseconds
+stateToMicroseconds OneSec     = 1000000 -- 1.0s delay
+stateToMicroseconds HalfSec    = 500000  -- 0.5s delay
+stateToMicroseconds FifthOfSec = 200000  -- 0.2s delay
+stateToMicroseconds TenthOfSec = 100000  -- 0.1s delay
+
+-- Cycle to the next blinking speed state
+nextSpeed :: LooptimeState -> LooptimeState 
+nextSpeed OneSec     = HalfSec
+nextSpeed HalfSec    = FifthOfSec
+nextSpeed FifthOfSec = TenthOfSec
+nextSpeed TenthOfSec = OneSec
+
+ledSettings :: Line.Settings -> IO ()
+ledSettings stgs = Line.setDirection stgs Line.DirOutput
+
+buttonSettings :: Line.Settings -> IO ()
+buttonSettings stgs = do
+  Line.setDirection stgs Line.DirInput     -- Configure line as input mode
+  Line.setBias stgs Line.BiasPullUp        -- Enable internal pull-up resistor
+                                           -- (the physical button connects GND when pressed, driving the line to Inactive)
+  Line.setDebouncePeriodUs stgs 80000      -- 80ms native kernel debounce period to filter out mechanical contact bounce without threadDelay
+  Line.setEdgeDetection stgs Line.EdgeFalling -- Listen for Falling edge transitions (button press to GND)
+
+-- Blinks the LED continuously using the delay duration read from the MVar
+ledWorker :: Line.Request -> MVar LooptimeState -> IO ()
+ledWorker req speedMVar = forever $ do
+  lts <- readMVar speedMVar
+  let delayUs = stateToMicroseconds lts
+  Line.setValue req ledOffset Line.Active
+  threadDelay delayUs
+  Line.setValue req ledOffset Line.Inactive
+  threadDelay delayUs
+
+-- Listens for button edge events and cycles the blinking speed state
+buttonWorker :: Line.Request -> Event.Buffer -> MVar LooptimeState -> IO ()
+buttonWorker req buf speedMVar = do
+  res <- Event.waitEvents req waitTimeoutNs
+  case res of
+    Event.EventReady readyReq -> do
+      _events <- Event.readEvents readyReq buf -- Read events from user buffer (configured with capacity 1)
+      modifyMVar_ speedMVar (return . nextSpeed)
+    Event.TimeoutResult -> threadDelay 20000 -- 20ms pause to yield file descriptor to LED worker thread
+
+main :: IO ()
+main = withGpioApp runApp
+
+runApp :: IO ()
+runApp = do
+  hSetBuffering stdout NoBuffering
+  initialSpeedMVar <- newMVar OneSec
+  withChip chipPath $ \chip -> do  
+    withSettings $ \buttonStgs -> do
+      buttonSettings buttonStgs
+      withSettings $ \ledStgs -> do 
+        ledSettings ledStgs
+        withConfig $ \config -> do
+          Line.addSettings config (singleton ledOffset) ledStgs
+          Line.addSettings config (singleton buttonOffset) buttonStgs
+          withRequest chip Nothing config $ \request -> do
+            withBuffer bufferCapacity $ \buffer -> do
+              Line.setValue request ledOffset Line.Inactive
+              putStrLn "Loop started: LED blinking concurrently. Press the button to change speed, or Ctrl+C to exit"
+              tid <- forkIO (forever $ ledWorker request initialSpeedMVar)
+              forever (buttonWorker request buffer initialSpeedMVar) `finally` killThread tid
diff --git a/examples/04-line-watch.hs b/examples/04-line-watch.hs
new file mode 100644
--- /dev/null
+++ b/examples/04-line-watch.hs
@@ -0,0 +1,69 @@
+-- In this example we will learn how to monitor GPIO line status changes across processes using chip watching.
+-- This example structures the application into modular helper functions ('runApp', 'monitorApp', 'lineApp'),
+-- serving as an intermediate modular phase before refactoring nested brackets with monadic continuation ('ContT').
+module Main where
+
+-- High-level resource brackets & exception handling
+import Fuyu.GPIO.Chip (Chip, withChip)
+import Fuyu.GPIO.Line (withRequest, withConfig, withSettings)
+import Fuyu.GPIO.Exception (withGpioApp)
+
+-- Qualified Domain Modules
+import qualified Fuyu.GPIO.Chip.Watch as Watch
+import qualified Fuyu.GPIO.Line as Line
+
+-- Base & third-party libraries
+import Control.Concurrent (forkIO, threadDelay)
+import Data.Vector.Storable (singleton)
+
+chipPath :: FilePath
+chipPath = "/dev/gpiochip0"
+
+targetOffset :: Line.Offset
+targetOffset = Line.Offset 257
+
+waitTimeoutNs :: Watch.Timeout
+waitTimeoutNs = Watch.Nanoseconds 5000000000 -- 5 seconds
+
+main :: IO ()
+main = withGpioApp $ do
+  putStrLn "Starting line status event monitor..."
+  runApp 
+  putStrLn "Line status event monitor completed successfully."
+
+-- 'runApp' centralizes worker threads and watchers to avoid pyramid of doom
+runApp :: IO ()
+runApp = do
+  putStrLn "Opening GPIO chip and starting line status watching..."
+  withChip chipPath $ \chip -> do
+    -- Register the line watch in the kernel before any interaction
+    -- (required so the kernel starts queueing status events for targetOffset)
+    Watch.withWatchLine chip targetOffset $ \_lineInfo -> do
+      _ <- forkIO $ lineApp chip
+      monitorApp chip 
+
+-- 'monitorApp' waits for status change events using 'waitEvent' and security token 'ReadyChip'
+monitorApp :: Chip -> IO ()
+monitorApp chip = do     
+  putStrLn "Waiting for line status change event (timeout: 5s)..."
+  res <- Watch.waitEvent chip waitTimeoutNs
+  case res of
+    -- Same pattern as 'Fuyu.GPIO.EdgeEvent.waitEvents'
+    Watch.EventReady readyChip -> do
+      Watch.withEvent readyChip $ \infoEvent -> do
+        -- In this case, we expect a 'Requested' info event type
+        eventType <- Watch.eventType infoEvent      
+        putStrLn ("Event received! " ++ show eventType)
+    Watch.TimeoutResult -> putStrLn "Wait timed out (timeout)."
+
+-- 'lineApp' simulates line interactions (requesting access to targetOffset) in a concurrent thread
+lineApp :: Chip -> IO () 
+lineApp chip = do   
+  withSettings $ \settings -> do
+    Line.setDirection settings Line.DirAsIs  
+    withConfig $ \config -> do
+      Line.addSettings config (singleton targetOffset) settings 
+      withRequest chip Nothing config $ \request -> do
+        name <- Line.chipName request
+        putStrLn ("Line request created successfully on chip: " ++ show name)
+        threadDelay 500000 -- Hold requested line briefly
diff --git a/examples/05-request-config.hs b/examples/05-request-config.hs
new file mode 100644
--- /dev/null
+++ b/examples/05-request-config.hs
@@ -0,0 +1,141 @@
+{-# LANGUAGE OverloadedStrings #-}
+-- In this example we will learn how to read a rotary encoder using a custom 'RequestConfig',
+-- flattening resource allocation brackets with monadic continuation ('ContT'), and managing state
+-- cleanly with 'Control.Monad.Trans.State.Strict' (StateT).
+module Main where
+
+-- High-level resource brackets & exception handling
+import Fuyu.GPIO.Chip (withChip)
+import qualified Fuyu.GPIO.Line as Line
+import qualified Fuyu.GPIO.EdgeEvent as EdgeEvent  
+import qualified Fuyu.GPIO.RequestConfig as ReqConf  
+import Fuyu.GPIO.Exception (withGpioApp)
+
+-- Base & third-party libraries
+import Control.Monad.Trans.Cont (evalContT, ContT(..))
+import Control.Monad.Trans.State.Strict (StateT, evalStateT, gets, modify')
+import Control.Monad.IO.Class (liftIO)
+import Control.Monad (forever, when)
+import qualified Data.Vector.Storable as V (fromList)
+import Data.List.NonEmpty (NonEmpty(..))
+
+chipPath :: FilePath
+chipPath = "/dev/gpiochip0"
+
+-- Line offsets for the rotary encoder signals
+offsetCLK :: Line.Offset
+offsetCLK = Line.Offset 256
+
+offsetDT :: Line.Offset
+offsetDT = Line.Offset 271
+
+-- Timeout for waiting on edge events (5 seconds)
+fiveSecondsNs :: EdgeEvent.Timeout
+fiveSecondsNs = EdgeEvent.Nanoseconds 5000000000
+
+-- Setting user buffer capacity to 1 guarantees that 'readEvents' returns exactly 1 event at a time.
+-- This simplifies pattern matching to '(ev :| _)' without losing any events in the kernel queue.
+capacity :: EdgeEvent.Capacity
+capacity = EdgeEvent.userBufferCapacity 1
+
+--------------------------------------------------------------------------------
+-- Encoder State Definition
+--------------------------------------------------------------------------------
+
+-- Clean pure Haskell record representing the quadrature state and step count.
+data EncoderState = EncoderState
+  { clkPin   :: !Int  -- Logical level of CLK line (1 = HIGH, 0 = LOW)
+  , dtPin    :: !Int  -- Logical level of DT line (1 = HIGH, 0 = LOW)
+  , position :: !Int  -- Accumulated rotary encoder step count
+  } deriving (Eq, Show)
+
+-- Initial state at startup (both lines idle at HIGH with 0 position count)
+initialState :: EncoderState
+initialState = EncoderState { clkPin = 1, dtPin = 1, position = 0 }
+
+main :: IO ()
+main = withGpioApp $ do
+  putStrLn "Starting request config example..."
+  runApp
+  putStrLn "Request config example completed successfully."
+
+--------------------------------------------------------------------------------
+-- Helper Configurator Brackets
+--------------------------------------------------------------------------------
+
+-- Encapsulates the creation and configuration of RequestConfig (consumer label & buffer size).
+withAppRequestConfig :: (ReqConf.RequestConfig -> IO r) -> IO r
+withAppRequestConfig action = ReqConf.withRequestConfig $ \reqconf -> do
+  ReqConf.setConsumer reqconf "encoder-app"
+  ReqConf.setBufferSize reqconf 256
+  action reqconf
+
+-- Encapsulates line settings configuration (input mode, 1ms debounce, edge detection).
+withAppLineSettings :: (Line.Settings -> IO r) -> IO r
+withAppLineSettings action = Line.withSettings $ \settings -> do
+  Line.setDirection settings Line.DirInput
+  Line.setDebouncePeriodUs settings 1000 -- 1ms debounce suitable for rotary encoder hardware
+  Line.setEdgeDetection settings Line.EdgeBoth 
+  action settings
+
+-- Encapsulates building line configuration for target pin offsets (CLK & DT).
+withAppLineConfig :: Line.Settings -> (Line.Config -> IO r) -> IO r
+withAppLineConfig settings action = Line.withConfig $ \config -> do
+  Line.addSettings config (V.fromList [offsetCLK, offsetDT]) settings
+  action config
+
+--------------------------------------------------------------------------------
+-- Resource Setup using ContT and Execution with StateT
+--------------------------------------------------------------------------------
+
+-- Monadic resource setup using 'ContT' flattens nested 'with...' brackets into a linear 'do' block.
+-- 'evalStateT' then runs the application loop with managed pure state ('EncoderState').
+runApp :: IO ()
+runApp = evalContT $ do
+  chip     <- ContT $ withChip chipPath
+  reqconf  <- ContT withAppRequestConfig
+  settings <- ContT withAppLineSettings
+  config   <- ContT $ withAppLineConfig settings
+  request  <- ContT $ Line.withRequest chip (Just reqconf) config
+  buffer   <- ContT $ EdgeEvent.withBuffer capacity
+
+  -- Run stateful application loop starting with 'initialState'
+  liftIO $ evalStateT (appLoop request buffer) initialState
+
+--------------------------------------------------------------------------------
+-- Encoder Application Loop using MonadState (StateT)
+--------------------------------------------------------------------------------
+
+-- Application loop running in 'StateT EncoderState IO ()'.
+appLoop :: Line.Request -> EdgeEvent.Buffer -> StateT EncoderState IO ()
+appLoop request buffer = forever $ do
+  result <- liftIO $ EdgeEvent.waitEvents request fiveSecondsNs
+  case result of
+    EdgeEvent.TimeoutResult -> 
+      liftIO $ putStrLn "No edge event was read (timeout)."
+
+    EdgeEvent.EventReady req -> do
+      (ev :| _) <- liftIO $ EdgeEvent.readEvents req buffer
+      oldPos    <- gets position
+      
+      -- Update pure state cleanly using strict 'modify''
+      modify' (updateEncoderState ev)
+      
+      newPos    <- gets position
+      when (newPos /= oldPos)
+        $ liftIO $ putStrLn $ "Encoder Position: " ++ show newPos
+
+-- Pure function that updates 'EncoderState' based on incoming 'EdgeEvent'.
+-- When CLK transitions to LOW (Falling edge), we inspect the current state of DT:
+--   - DT == 1 (HIGH) -> Clockwise rotation (+1)
+--   - DT == 0 (LOW)  -> Counter-Clockwise rotation (-1)
+updateEncoderState :: EdgeEvent.EdgeEvent -> EncoderState -> EncoderState
+updateEncoderState (EdgeEvent.EdgeEvent offset evType _) st = case (offset, evType) of
+  (Line.Offset 256, EdgeEvent.Falling) ->
+    let delta  = if dtPin st == 1 then 1 else (-1)
+    in st { clkPin = 0, position = position st + delta }
+
+  (Line.Offset 256, EdgeEvent.Rising)  -> st { clkPin = 1 }
+  (Line.Offset 271, EdgeEvent.Falling) -> st { dtPin = 0 }
+  (Line.Offset 271, EdgeEvent.Rising)  -> st { dtPin = 1 }
+  _                                    -> st
diff --git a/fuyu-gpio.cabal b/fuyu-gpio.cabal
new file mode 100644
--- /dev/null
+++ b/fuyu-gpio.cabal
@@ -0,0 +1,95 @@
+cabal-version:      3.0
+name:               fuyu-gpio
+version:            0.0.9.0
+synopsis:           High-level, type-safe interface for Linux GPIO (libgpiod v2).
+description:        High-level, type-safe, and resource-managed Haskell interface for Linux GPIO character devices using libgpiod v2. Built on top of fuyu-gpio-direct, fuyu-gpio provides automatic memory management (bracket / with* style), typed exception handling, metadata snapshots, and zero-copy vector operations for high-performance GPIO I/O.
+license:            LGPL-2.1-or-later
+license-file:       LICENSE
+author:             BassGT
+maintainer:         sebastian11medrano@gmail.com
+-- copyright:
+category:           System, Hardware
+build-type:         Simple
+extra-doc-files:    CHANGELOG.md
+
+source-repository head
+    type:     git
+    location: https://github.com/BassGT/fuyu-gpio.git
+
+common warnings
+    ghc-options: -Wall
+
+library
+    import:           warnings
+    exposed-modules:  Fuyu.GPIO,
+                      Fuyu.GPIO.Chip,
+                      Fuyu.GPIO.Chip.Info,
+                      Fuyu.GPIO.Chip.Info.Unsafe,
+                      Fuyu.GPIO.Chip.Unsafe,
+                      Fuyu.GPIO.Line,
+                      Fuyu.GPIO.Line.Info,
+                      Fuyu.GPIO.Line.Info.Unsafe,
+                      Fuyu.GPIO.Line.Unsafe,
+                      Fuyu.GPIO.RequestConfig,
+                      Fuyu.GPIO.RequestConfig.Unsafe,
+                      Fuyu.GPIO.EdgeEvent,
+                      Fuyu.GPIO.EdgeEvent.Unsafe,
+                      Fuyu.GPIO.Chip.Watch,
+                      Fuyu.GPIO.Chip.Watch.Unsafe,
+                      Fuyu.GPIO.Exception
+
+    other-modules:    Fuyu.GPIO.Types
+
+    -- other-extensions:
+    build-depends:    base >= 4.18 && < 5,
+                      fuyu-gpio-direct ^>=0.1.0.0,
+                      bytestring ^>=0.12.1.0,
+                      vector ^>=0.13.1.0
+
+
+    hs-source-dirs:   src
+    default-language: Haskell2010
+    default-extensions: PatternSynonyms
+
+
+flag examples
+  description: Compile all examples
+  default:     False
+  manual:      True
+
+common example-config
+    import:           warnings
+    hs-source-dirs:   examples
+    default-language: Haskell2010
+    if !flag(examples)
+        buildable: False
+    else
+        build-depends:
+            base >= 4.18 && < 5,
+            fuyu-gpio,
+            vector ^>= 0.13.1.0,
+            transformers >= 0.5 && < 0.7,
+            bytestring ^>= 0.12.1.0
+
+
+executable 01-blink
+    import:           example-config
+    main-is:          01-blink.hs
+    
+executable 02-button 
+    import:           example-config
+    main-is:          02-button.hs
+    
+executable 03-led-and-button
+    import:           example-config
+    main-is:          03-led-and-button.hs
+    ghc-options:      -threaded 
+
+executable 04-line-watch
+    import:           example-config
+    main-is:          04-line-watch.hs
+    ghc-options:      -threaded -rtsopts
+    
+executable 05-request-config  
+    import:           example-config
+    main-is:          05-request-config.hs
diff --git a/src/Fuyu/GPIO.hs b/src/Fuyu/GPIO.hs
new file mode 100644
--- /dev/null
+++ b/src/Fuyu/GPIO.hs
@@ -0,0 +1,30 @@
+-- |
+-- Module      : Fuyu.GPIO
+-- Description : High-level, type-safe Haskell interface for Linux GPIO (libgpiod v2).
+-- Maintainer  : BassGT
+-- Stability   : experimental
+-- Portability : POSIX (Linux GPIO character device interface)
+--
+-- This is the main umbrella module for @fuyu-gpio@, providing high-level,
+-- managed resource wrappers ('withChip', 'withSettings', 'withConfig', 'withRequest')
+-- and exception handling ('GpioException', 'withGpioApp') for Linux GPIO character devices.
+--
+-- For detailed metadata inspection, import "Fuyu.GPIO.Chip.Info" or "Fuyu.GPIO.Line.Info" qualified.
+-- For manual/unmanaged FFI resource lifecycle, import the corresponding @.Unsafe@ submodules.
+module Fuyu.GPIO
+  ( -- * Domain Modules
+    module Fuyu.GPIO.Chip
+  , module Fuyu.GPIO.Line
+  , module Fuyu.GPIO.RequestConfig
+  , module Fuyu.GPIO.EdgeEvent
+
+    -- * Exception & App Runner
+  , GpioException(..)
+  , withGpioApp
+  ) where
+
+import Fuyu.GPIO.Chip hiding (fd, eventType, timestampNs)
+import Fuyu.GPIO.Line hiding (fd)
+import Fuyu.GPIO.RequestConfig
+import Fuyu.GPIO.EdgeEvent
+import Fuyu.GPIO.Exception (GpioException(..), withGpioApp)
diff --git a/src/Fuyu/GPIO/Chip.hs b/src/Fuyu/GPIO/Chip.hs
new file mode 100644
--- /dev/null
+++ b/src/Fuyu/GPIO/Chip.hs
@@ -0,0 +1,66 @@
+-- |
+-- Module      : Fuyu.GPIO.Chip
+-- Description : High-level operations for GPIO chips and line watching.
+-- Maintainer  : BassGT
+-- Stability   : experimental
+-- Portability : POSIX (Linux gpiod v2)
+--
+-- This module provides managed resource brackets ('withChip') for opening and closing
+-- Linux GPIO chips safely, as well as functions for watching line status changes.
+module Fuyu.GPIO.Chip
+  ( -- * Operations & Brackets
+    withChip
+  , withChipInfo
+  , withLineInfo
+  , path
+  , offsetFromName
+  , fd
+
+    -- * Line Watch & Info Events
+  , module Fuyu.GPIO.Chip.Watch
+
+    -- * General Utilities
+  , isGPIOChip
+  , gpiodAPIVersion
+  ) where
+
+import Control.Exception (bracket)
+import Data.ByteString (ByteString)
+import qualified Data.ByteString.Char8 as BS8
+import System.Posix.Types (Fd)
+import qualified Fuyu.GPIO.Direct as D
+import Fuyu.GPIO.Chip.Info (withChipInfo)
+import Fuyu.GPIO.Line.Info (withLineInfo)
+import Fuyu.GPIO.Chip.Unsafe (openChip, closeChip)
+import Fuyu.GPIO.Chip.Watch
+import Fuyu.GPIO.Exception
+
+-- | Open a GPIO chip by filesystem path (e.g. "/dev/gpiochip0") and automatically close it when finished.
+withChip :: FilePath -> (Chip -> IO a) -> IO a
+withChip path' = bracket (openChip path') closeChip
+
+-- | Retrieve chip filesystem path as a 'ByteString'.
+path :: Chip -> IO ByteString
+path chip = unwrapOrThrow ChipInfoFailed (D.chipPath chip)
+
+-- | Map a GPIO line name (e.g. "GPIO17") to its numeric 'Offset' on the chip.
+offsetFromName :: Chip -> ByteString -> IO Offset
+offsetFromName chip name = unwrapOrThrow LineInfoFailed (D.chipLineOffsetFromName chip name)
+
+-- | Get the underlying Linux file descriptor associated with the GPIO chip handle.
+fd :: Chip -> IO Fd
+fd = D.chipFd
+
+--------------------------------------------------------------------------------
+-- General Utilities
+--------------------------------------------------------------------------------
+-- General Utilities
+--------------------------------------------------------------------------------
+
+-- | Check if the given filesystem path is a valid GPIO chip character device.
+isGPIOChip :: FilePath -> IO Bool
+isGPIOChip = D.isGPIOChip . BS8.pack
+
+-- | Retrieve the underlying libgpiod C API version string (e.g. "2.1").
+gpiodAPIVersion :: IO ByteString
+gpiodAPIVersion = D.gpiodAPIVersion
diff --git a/src/Fuyu/GPIO/Chip/Info.hs b/src/Fuyu/GPIO/Chip/Info.hs
new file mode 100644
--- /dev/null
+++ b/src/Fuyu/GPIO/Chip/Info.hs
@@ -0,0 +1,47 @@
+-- |
+-- Module      : Fuyu.GPIO.Chip.Info
+-- Description : Read-only metadata query functions for ChipInfo.
+-- Maintainer  : BassGT
+-- Stability   : experimental
+-- Portability : POSIX (Linux gpiod v2)
+--
+-- This module provides functions to inspect 'ChipInfo' snapshots.
+-- It is designed to be imported qualified:
+--
+-- @
+-- import qualified Fuyu.GPIO.Chip.Info as ChipInfo
+-- @
+module Fuyu.GPIO.Chip.Info
+  ( -- * Types
+    ChipInfo
+
+    -- * Managed Resource Allocation
+  , withChipInfo
+
+    -- * Metadata Accessors
+  , name
+  , label
+  , numLines
+  ) where
+
+import Control.Exception (bracket)
+import Data.ByteString (ByteString)
+import qualified Fuyu.GPIO.Direct as D
+import Fuyu.GPIO.Chip.Info.Unsafe (chipInfo, freeChipInfo)
+import Fuyu.GPIO.Types
+
+-- | Retrieve information about a GPIO chip and free it automatically afterwards.
+withChipInfo :: Chip -> (ChipInfo -> IO a) -> IO a
+withChipInfo chip = bracket (chipInfo chip) freeChipInfo
+
+-- | Get the name of the GPIO chip (e.g. "gpiochip4").
+name :: ChipInfo -> IO ByteString
+name = D.chipInfoName
+
+-- | Get the label of the GPIO chip.
+label :: ChipInfo -> IO ByteString
+label = D.chipInfoLabel
+
+-- | Get the total number of lines exposed by the GPIO chip.
+numLines :: ChipInfo -> IO Word
+numLines = D.chipInfoNumLines
diff --git a/src/Fuyu/GPIO/Chip/Info/Unsafe.hs b/src/Fuyu/GPIO/Chip/Info/Unsafe.hs
new file mode 100644
--- /dev/null
+++ b/src/Fuyu/GPIO/Chip/Info/Unsafe.hs
@@ -0,0 +1,29 @@
+-- |
+-- Module      : Fuyu.GPIO.Chip.Info.Unsafe
+-- Description : Unsafe manual resource allocation for ChipInfo.
+-- Maintainer  : BassGT
+-- Stability   : experimental
+-- Portability : POSIX (Linux gpiod v2)
+--
+-- Manual resource allocation ('chipInfo', 'freeChipInfo') for 'ChipInfo' handles.
+module Fuyu.GPIO.Chip.Info.Unsafe
+  ( -- * Types
+    ChipInfo
+
+    -- * Unsafe Manual Resource Allocation
+  , chipInfo
+  , freeChipInfo
+  ) where
+
+import qualified Fuyu.GPIO.Direct as D
+import Fuyu.GPIO.Exception
+import Fuyu.GPIO.Types
+
+-- | Retrieve chip info directly.
+-- Must be manually freed using 'freeChipInfo'.
+chipInfo :: Chip -> IO ChipInfo
+chipInfo chip = unwrapOrThrow ChipInfoFailed (D.chipInfo chip)
+
+-- | Free a 'ChipInfo' handle.
+freeChipInfo :: ChipInfo -> IO ()
+freeChipInfo = D.chipInfoFree
diff --git a/src/Fuyu/GPIO/Chip/Unsafe.hs b/src/Fuyu/GPIO/Chip/Unsafe.hs
new file mode 100644
--- /dev/null
+++ b/src/Fuyu/GPIO/Chip/Unsafe.hs
@@ -0,0 +1,36 @@
+-- |
+-- Module      : Fuyu.GPIO.Chip.Unsafe
+-- Description : Unsafe manual resource allocation for GPIO chips and info events.
+-- Maintainer  : BassGT
+-- Stability   : experimental
+-- Portability : POSIX (Linux gpiod v2)
+--
+-- Manual resource allocation ('openChip', 'closeChip', 'readInfoEvent', 'freeInfoEvent')
+-- for applications that cannot use managed bracket functions.
+module Fuyu.GPIO.Chip.Unsafe
+  ( -- * Types
+    Chip
+  , InfoEvent
+
+    -- * Unsafe Manual Resource Allocation
+  , openChip
+  , closeChip
+  , readInfoEvent
+  , freeInfoEvent
+  ) where
+
+import qualified Data.ByteString.Char8 as BS8
+import qualified Fuyu.GPIO.Direct as D
+import Fuyu.GPIO.Chip.Watch.Unsafe (readInfoEvent, freeInfoEvent)
+import Fuyu.GPIO.Exception
+import Fuyu.GPIO.Types
+
+-- | Open a GPIO chip by its filesystem path (e.g. "/dev/gpiochip4").
+-- Must be manually closed using 'closeChip'.
+openChip :: FilePath -> IO Chip
+openChip path = unwrapOrThrow (ChipOpenFailed path) (D.chipOpen (BS8.pack path))
+
+-- | Close a GPIO chip handle.
+closeChip :: Chip -> IO ()
+closeChip = D.chipClose
+
diff --git a/src/Fuyu/GPIO/Chip/Watch.hs b/src/Fuyu/GPIO/Chip/Watch.hs
new file mode 100644
--- /dev/null
+++ b/src/Fuyu/GPIO/Chip/Watch.hs
@@ -0,0 +1,99 @@
+{-# LANGUAGE PatternSynonyms #-}
+
+-- |
+-- Module      : Fuyu.GPIO.Chip.Watch
+-- Description : Operations for watching GPIO line status events.
+-- Maintainer  : BassGT
+-- Stability   : experimental
+-- Portability : POSIX (Linux gpiod v2)
+--
+-- This module provides managed resource brackets ('withWatchLine', 'withEvent')
+-- and functions for watching line status changes (e.g. requested, released, reconfigured).
+--
+-- It is designed to be imported qualified or used via top-level "Fuyu.GPIO":
+--
+-- @
+-- import qualified Fuyu.GPIO.Chip.Watch as Watch
+-- @
+module Fuyu.GPIO.Chip.Watch
+  ( -- * Types & Patterns
+    Chip
+  , ReadyChip(..)
+  , readyToChip
+  , WaitResult(..)
+  , LineInfo
+  , Offset
+  , pattern Offset
+  , Timeout
+  , pattern Nanoseconds
+  , pattern Immediate
+  , pattern Infinite
+  , Timestamp
+  , InfoEvent
+  , InfoEventType
+  , pattern Requested
+  , pattern Released
+  , pattern ConfigChanged
+
+    -- * Managed Brackets
+  , withWatchLine
+  , withEvent
+
+    -- * Line Watch Operations
+  , watchLine
+  , unwatchLine
+  , waitEvent
+
+    -- * InfoEvent Accessors
+  , eventType
+  , timestampNs
+  , lineInfo
+  ) where
+
+import Control.Exception (bracket)
+import qualified Fuyu.GPIO.Direct as D
+import Fuyu.GPIO.Chip.Watch.Unsafe (readInfoEvent, freeInfoEvent)
+import Fuyu.GPIO.Exception
+import Fuyu.GPIO.Types hiding (eventType)
+
+-- | Start watching a line for status change events (e.g. requested, released, reconfigured)
+-- within a bracket, automatically unwatching the line when finished.
+--
+-- Passes the initial 'LineInfo' snapshot of the line to the callback.
+withWatchLine :: Chip -> Offset -> (LineInfo -> IO a) -> IO a
+withWatchLine chip offset' = bracket (watchLine chip offset') (\_ -> unwatchLine chip offset')
+
+-- | Start watching a line for status change events (e.g. requested, released, reconfigured).
+-- Returns the initial 'LineInfo' snapshot of the line.
+watchLine :: Chip -> Offset -> IO LineInfo
+watchLine chip offset' = unwrapOrThrow LineInfoFailed (D.chipWatchLineInfo chip offset')
+
+-- | Stop watching a line for status change events.
+unwatchLine :: Chip -> Offset -> IO ()
+unwatchLine chip offset' = unwrapOrThrow LineInfoFailed (D.chipUnwatchLineInfo chip offset')
+
+-- | Wait for status change info events on any of the watched lines on the chip until the specified timeout.
+-- Throws 'WaitInfoEventFailed' on error.
+waitEvent :: Chip -> Timeout -> IO (WaitResult ReadyChip)
+waitEvent chip timeout = do
+  res <- unwrapOrThrow WaitInfoEventFailed (D.chipWaitInfoEvent chip timeout)
+  pure $ case res of
+    D.EventReady -> EventReady (ReadyChip chip)
+    D.Timeout    -> TimeoutResult
+
+-- | Read a status change info event from a chip once 'waitEvent' indicates it is ready,
+-- and automatically free it afterwards.
+withEvent :: ReadyChip -> (InfoEvent -> IO a) -> IO a
+withEvent readyChip = bracket (readInfoEvent readyChip) freeInfoEvent
+
+-- | Get the event type of an 'InfoEvent' ('Requested', 'Released', 'ConfigChanged').
+eventType :: InfoEvent -> IO InfoEventType
+eventType = D.infoEventType
+
+-- | Get the timestamp in nanoseconds of an 'InfoEvent'.
+timestampNs :: InfoEvent -> IO Timestamp
+timestampNs = D.infoEventTimestamp
+
+-- | Get the line info snapshot associated with an 'InfoEvent'.
+lineInfo :: InfoEvent -> IO LineInfo
+lineInfo = D.infoEventLineInfo
diff --git a/src/Fuyu/GPIO/Chip/Watch/Unsafe.hs b/src/Fuyu/GPIO/Chip/Watch/Unsafe.hs
new file mode 100644
--- /dev/null
+++ b/src/Fuyu/GPIO/Chip/Watch/Unsafe.hs
@@ -0,0 +1,33 @@
+-- |
+-- Module      : Fuyu.GPIO.Chip.Watch.Unsafe
+-- Description : Unsafe manual resource allocation for line info events.
+-- Maintainer  : BassGT
+-- Stability   : experimental
+-- Portability : POSIX (Linux gpiod v2)
+--
+-- Manual resource allocation ('readInfoEvent', 'freeInfoEvent')
+-- for applications that cannot use managed bracket functions.
+module Fuyu.GPIO.Chip.Watch.Unsafe
+  ( -- * Types & Security Token
+    Chip
+  , ReadyChip(..)
+  , readyToChip
+  , InfoEvent
+
+    -- * Unsafe Manual Resource Allocation
+  , readInfoEvent
+  , freeInfoEvent
+  ) where
+
+import qualified Fuyu.GPIO.Direct as D
+import Fuyu.GPIO.Exception
+import Fuyu.GPIO.Types
+
+-- | Read a line info event from a chip after 'Fuyu.GPIO.Chip.Watch.waitEvent' confirms it is ready.
+-- Must be manually freed using 'freeInfoEvent'.
+readInfoEvent :: ReadyChip -> IO InfoEvent
+readInfoEvent (ReadyChip chip) = unwrapOrThrow ReadInfoEventFailed (D.chipReadInfoEvent chip)
+
+-- | Free an info event object.
+freeInfoEvent :: InfoEvent -> IO ()
+freeInfoEvent = D.infoEventFree
diff --git a/src/Fuyu/GPIO/EdgeEvent.hs b/src/Fuyu/GPIO/EdgeEvent.hs
new file mode 100644
--- /dev/null
+++ b/src/Fuyu/GPIO/EdgeEvent.hs
@@ -0,0 +1,144 @@
+{-# LANGUAGE PatternSynonyms #-}
+
+-- |
+-- Module      : Fuyu.GPIO.EdgeEvent
+-- Description : High-level edge event waiting, reading, and buffer management.
+-- Maintainer  : BassGT
+-- Stability   : experimental
+-- Portability : POSIX (Linux gpiod v2)
+--
+-- This module provides managed resource brackets ('withBuffer') and functions for waiting
+-- on edge events ('waitEvents') and reading them ('readEvents') securely using the
+-- 'ReadyRequest' capability token.
+module Fuyu.GPIO.EdgeEvent
+  ( -- * Security Token & Wait Result
+    WaitResult(..)
+  , ReadyRequest(..)
+  , readyToRequest
+
+    -- * Buffer & Event Types
+  , Buffer
+  , Capacity
+  , userBufferCapacity
+  , capacity
+  , Event
+  , Timeout
+  , pattern Nanoseconds
+  , pattern Immediate
+  , pattern Infinite
+  , Timestamp
+  , EdgeEventType
+  , pattern Rising
+  , pattern Falling
+
+    -- * Event Data Type & Parser
+  , NonEmpty(..)
+  , EdgeEvent(..)
+  , parseEvent
+
+    -- * Event Buffer Operations (Managed)
+  , withBuffer
+  , bufferCapacity
+  , bufferNumEvents
+  , bufferEvent
+
+    -- * Waiting & Reading Events
+  , waitEvents
+  , readEvents
+  , withRawEvents
+
+    -- * RawEdgeEvent Metadata Accessors
+  , eventType
+  , timestampNs
+  , lineOffset
+  , globalSeqNo
+  , lineSeqNo
+  , copyEvent
+  ) where
+
+import Control.Exception (bracket)
+import Control.Monad (forM)
+import Data.List.NonEmpty (NonEmpty(..))
+import qualified Data.List.NonEmpty as NE
+import Data.Word (Word64)
+import qualified Fuyu.GPIO.Direct as D
+import Fuyu.GPIO.EdgeEvent.Unsafe (newEventBuffer, freeEventBuffer, readEventsRaw)
+import Fuyu.GPIO.Exception
+import Fuyu.GPIO.Types hiding (eventType)
+
+-- | Allocate an edge event buffer of the specified capacity and free it automatically afterwards.
+withBuffer :: Capacity -> (Buffer -> IO a) -> IO a
+withBuffer capacity' = bracket (newEventBuffer capacity') freeEventBuffer
+
+-- | Get the capacity of an event buffer.
+bufferCapacity :: Buffer -> IO Capacity
+bufferCapacity buf = userBufferCapacity <$> D.eventBufferCapacity buf
+
+-- | Get the number of events currently stored in an event buffer.
+bufferNumEvents :: Buffer -> IO Word
+bufferNumEvents = D.eventBufferNumEvents
+
+-- | Get a specific edge event from the buffer by index.
+bufferEvent :: Buffer -> Word -> IO Event
+bufferEvent buf idx = unwrapOrThrow ReadEdgeEventsFailed (D.eventBufferGetEvent buf idx)
+
+-- | Wait for edge events to occur on requested lines until the specified timeout.
+-- Throws 'WaitEdgeEventsFailed' on error.
+waitEvents :: Request -> Timeout -> IO (WaitResult ReadyRequest)
+waitEvents req timeout = do
+  res <- unwrapOrThrow WaitEdgeEventsFailed (D.lineRequestWaitEdgeEvents req timeout)
+  pure $ case res of
+    D.EventReady -> EventReady (ReadyRequest req)
+    D.Timeout    -> TimeoutResult
+
+-- | Parse a raw edge event pointer into a pure Haskell 'EdgeEvent' structure.
+parseEvent :: Event -> IO EdgeEvent
+parseEvent ev = EdgeEvent
+  <$> D.rawEdgeEventLineOffset ev
+  <*> D.rawEdgeEventType ev
+  <*> D.rawEdgeEventTimestampNs ev
+
+-- | Read buffered edge events once 'waitEvents' indicates they are ready,
+-- parsing them into a non-empty list of pure 'EdgeEvent' structures.
+readEvents :: ReadyRequest -> Buffer -> IO (NonEmpty EdgeEvent)
+readEvents readyReq buf = withRawEvents readyReq buf parseEvent
+
+-- | Process raw edge events directly in the buffer using a callback without intermediate allocations,
+-- returning a non-empty list of results.
+withRawEvents :: ReadyRequest -> Buffer -> (Event -> IO a) -> IO (NonEmpty a)
+withRawEvents readyReq buf action = do
+  count <- readEventsRaw readyReq buf
+  results <- forM [0 .. count - 1] $ \idx -> do
+    ev <- bufferEvent buf (fromIntegral idx)
+    action ev
+  case NE.nonEmpty results of
+    Just ne -> pure ne
+    Nothing -> ioError (userError "readEvents: expected at least one event from ReadyRequest but got none")
+
+--------------------------------------------------------------------------------
+-- RawEdgeEvent Metadata Accessors
+--------------------------------------------------------------------------------
+
+-- | Get the type of event ('Rising' or 'Falling').
+eventType :: Event -> IO EdgeEventType
+eventType = D.rawEdgeEventType
+
+-- | Get the event timestamp in nanoseconds.
+timestampNs :: Event -> IO Timestamp
+timestampNs = D.rawEdgeEventTimestampNs
+
+-- | Get the offset of the line that triggered the event.
+lineOffset :: Event -> IO Offset
+lineOffset = D.rawEdgeEventLineOffset
+
+-- | Get the global sequence number of the event.
+globalSeqNo :: Event -> IO Word64
+globalSeqNo = D.rawEdgeEventGlobalSeqNo
+
+-- | Get the line-specific sequence number of the event.
+lineSeqNo :: Event -> IO Offset
+lineSeqNo = D.rawEdgeEventLineSeqNo
+
+-- | Make a copy of a raw edge event object.
+copyEvent :: Event -> IO Event
+copyEvent ev = unwrapOrThrow RawEdgeEventCopyFailed (D.rawEdgeEventCopy ev)
diff --git a/src/Fuyu/GPIO/EdgeEvent/Unsafe.hs b/src/Fuyu/GPIO/EdgeEvent/Unsafe.hs
new file mode 100644
--- /dev/null
+++ b/src/Fuyu/GPIO/EdgeEvent/Unsafe.hs
@@ -0,0 +1,42 @@
+-- |
+-- Module      : Fuyu.GPIO.EdgeEvent.Unsafe
+-- Description : Unsafe manual resource allocation and raw buffer reading for event buffers.
+-- Maintainer  : BassGT
+-- Stability   : experimental
+-- Portability : POSIX (Linux gpiod v2)
+--
+-- Manual resource allocation ('newEventBuffer', 'freeEventBuffer') and raw reading ('readEventsRaw') for edge 'Buffer' handles.
+module Fuyu.GPIO.EdgeEvent.Unsafe
+  ( -- * Types & Security Token
+    Buffer
+  , Capacity
+  , userBufferCapacity
+  , capacity
+  , ReadyRequest(..)
+
+    -- * Unsafe Manual Resource Allocation & Reading
+  , newEventBuffer
+  , freeEventBuffer
+  , readEventsRaw
+  ) where
+
+import qualified Fuyu.GPIO.Direct as D
+import Fuyu.GPIO.Exception
+import Fuyu.GPIO.Types
+
+-- | Allocate an edge event buffer of the specified capacity.
+-- Must be manually freed with 'freeEventBuffer'.
+newEventBuffer :: Capacity -> IO Buffer
+newEventBuffer cap = unwrapOrThrow EventBufferNewFailed (D.eventBufferNew (capacity cap))
+
+-- | Free an edge event buffer object.
+freeEventBuffer :: Buffer -> IO ()
+freeEventBuffer = D.eventBufferFree
+
+-- | Read raw edge events into the buffer and return the number of events read.
+-- Automatically uses the buffer's full capacity.
+-- Throws 'ReadEdgeEventsFailed' on error.
+readEventsRaw :: ReadyRequest -> Buffer -> IO Int
+readEventsRaw (ReadyRequest req) buf = do
+  cap <- D.eventBufferCapacity buf
+  unwrapOrThrow ReadEdgeEventsFailed (D.lineRequestReadEdgeEvents req buf cap)
diff --git a/src/Fuyu/GPIO/Exception.hs b/src/Fuyu/GPIO/Exception.hs
new file mode 100644
--- /dev/null
+++ b/src/Fuyu/GPIO/Exception.hs
@@ -0,0 +1,116 @@
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+-- |
+-- Module      : Fuyu.GPIO.Exception
+-- Description : Exception types and high-level application handler for fuyu-gpio operations.
+-- Maintainer  : BassGT
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- High-level exception type 'GpioException' thrown by fuyu-gpio operations,
+-- and managed application runner 'withGpioApp' for graceful signal handling.
+module Fuyu.GPIO.Exception
+  ( GpioException(..)
+  , unwrapOrThrow
+  , withGpioApp
+  ) where
+
+import Control.Exception (Exception, SomeException, catch, fromException, throwIO, AsyncException(UserInterrupt))
+import Foreign.C.Error (Errno(..))
+import Control.Monad (void)
+
+-- | High-level exceptions thrown by fuyu-gpio operations.
+data GpioException
+  = ChipOpenFailed FilePath Errno
+  | ChipInfoFailed Errno
+  | LineInfoFailed Errno
+  | LineSettingsNewFailed Errno
+  | LineSettingsSetFailed Errno
+  | LineConfigNewFailed Errno
+  | RequestConfigNewFailed Errno
+  | LineRequestFailed Errno
+  | EventBufferNewFailed Errno
+  | LineValueReadFailed Errno
+  | LineValueWriteFailed Errno
+  | LineReconfigureFailed Errno
+  | WaitEdgeEventsFailed Errno
+  | ReadEdgeEventsFailed Errno
+  | WaitInfoEventFailed Errno
+  | ReadInfoEventFailed Errno
+  | RawEdgeEventCopyFailed Errno
+  | LineInfoCopyFailed Errno
+  | CustomGpioError String Errno
+  | InvalidArgument String
+  deriving (Exception)
+
+instance Eq GpioException where
+  ChipOpenFailed p1 e1 == ChipOpenFailed p2 e2 = p1 == p2 && e1 == e2
+  ChipInfoFailed e1 == ChipInfoFailed e2 = e1 == e2
+  LineInfoFailed e1 == LineInfoFailed e2 = e1 == e2
+  LineSettingsNewFailed e1 == LineSettingsNewFailed e2 = e1 == e2
+  LineSettingsSetFailed e1 == LineSettingsSetFailed e2 = e1 == e2
+  LineConfigNewFailed e1 == LineConfigNewFailed e2 = e1 == e2
+  RequestConfigNewFailed e1 == RequestConfigNewFailed e2 = e1 == e2
+  LineRequestFailed e1 == LineRequestFailed e2 = e1 == e2
+  EventBufferNewFailed e1 == EventBufferNewFailed e2 = e1 == e2
+  LineValueReadFailed e1 == LineValueReadFailed e2 = e1 == e2
+  LineValueWriteFailed e1 == LineValueWriteFailed e2 = e1 == e2
+  LineReconfigureFailed e1 == LineReconfigureFailed e2 = e1 == e2
+  WaitEdgeEventsFailed e1 == WaitEdgeEventsFailed e2 = e1 == e2
+  ReadEdgeEventsFailed e1 == ReadEdgeEventsFailed e2 = e1 == e2
+  WaitInfoEventFailed e1 == WaitInfoEventFailed e2 = e1 == e2
+  ReadInfoEventFailed e1 == ReadInfoEventFailed e2 = e1 == e2
+  RawEdgeEventCopyFailed e1 == RawEdgeEventCopyFailed e2 = e1 == e2
+  LineInfoCopyFailed e1 == LineInfoCopyFailed e2 = e1 == e2
+  CustomGpioError s1 e1 == CustomGpioError s2 e2 = s1 == s2 && e1 == e2
+  InvalidArgument msg1 == InvalidArgument msg2 = msg1 == msg2
+  _ == _ = False
+
+instance Show GpioException where
+  show (ChipOpenFailed path (Errno e)) = "ChipOpenFailed: Failed to open chip at '" ++ path ++ "' (errno " ++ show e ++ ")"
+  show (ChipInfoFailed (Errno e)) = "ChipInfoFailed (errno " ++ show e ++ ")"
+  show (LineInfoFailed (Errno e)) = "LineInfoFailed (errno " ++ show e ++ ")"
+  show (LineSettingsNewFailed (Errno e)) = "LineSettingsNewFailed (errno " ++ show e ++ ")"
+  show (LineSettingsSetFailed (Errno e)) = "LineSettingsSetFailed (errno " ++ show e ++ ")"
+  show (LineConfigNewFailed (Errno e)) = "LineConfigNewFailed (errno " ++ show e ++ ")"
+  show (RequestConfigNewFailed (Errno e)) = "RequestConfigNewFailed (errno " ++ show e ++ ")"
+  show (LineRequestFailed (Errno e)) = "LineRequestFailed (errno " ++ show e ++ ")"
+  show (EventBufferNewFailed (Errno e)) = "EventBufferNewFailed (errno " ++ show e ++ ")"
+  show (LineValueReadFailed (Errno e)) = "LineValueReadFailed (errno " ++ show e ++ ")"
+  show (LineValueWriteFailed (Errno e)) = "LineValueWriteFailed (errno " ++ show e ++ ")"
+  show (LineReconfigureFailed (Errno e)) = "LineReconfigureFailed (errno " ++ show e ++ ")"
+  show (WaitEdgeEventsFailed (Errno e)) = "WaitEdgeEventsFailed (errno " ++ show e ++ ")"
+  show (ReadEdgeEventsFailed (Errno e)) = "ReadEdgeEventsFailed (errno " ++ show e ++ ")"
+  show (WaitInfoEventFailed (Errno e)) = "WaitInfoEventFailed (errno " ++ show e ++ ")"
+  show (ReadInfoEventFailed (Errno e)) = "ReadInfoEventFailed (errno " ++ show e ++ ")"
+  show (RawEdgeEventCopyFailed (Errno e)) = "RawEdgeEventCopyFailed (errno " ++ show e ++ ")"
+  show (LineInfoCopyFailed (Errno e)) = "LineInfoCopyFailed (errno " ++ show e ++ ")"
+  show (CustomGpioError msg (Errno e)) = "CustomGpioError '" ++ msg ++ "' (errno " ++ show e ++ ")"
+  show (InvalidArgument msg) = "InvalidArgument: " ++ msg
+
+-- | Helper to unwrap an 'Either Errno a' from low-level FFI calls or throw a 'GpioException'.
+unwrapOrThrow :: (Errno -> GpioException) -> IO (Either Errno a) -> IO a
+unwrapOrThrow mkExc action = do
+  res <- action
+  case res of
+    Left errno -> throwIO (mkExc errno)
+    Right val  -> pure val
+
+-- | High-level managed application runner.
+-- Automatically handles 'Ctrl+C' ('UserInterrupt'), interrupted system calls ('EINTR' / 'WaitEdgeEventsFailed'),
+-- and prints formatted 'GpioException' messages cleanly without uncaught backtraces.
+withGpioApp :: IO a -> IO ()
+withGpioApp action = void action `catch` handleAppException
+  where
+    handleAppException :: SomeException -> IO ()
+    handleAppException exc
+      | isUserInterrupt exc = putStrLn "\nLoop terminated successfully!"
+      | Just (WaitEdgeEventsFailed (Errno 4)) <- fromException exc = putStrLn "\nLoop terminated successfully!"
+      | Just (gpioErr :: GpioException) <- fromException exc = putStrLn $ "\n[GPIO Exception]: " ++ show gpioErr
+      | otherwise = throwIO exc
+
+    isUserInterrupt :: SomeException -> Bool
+    isUserInterrupt e = case fromException e of
+      Just UserInterrupt -> True
+      _                  -> False
diff --git a/src/Fuyu/GPIO/Line.hs b/src/Fuyu/GPIO/Line.hs
new file mode 100644
--- /dev/null
+++ b/src/Fuyu/GPIO/Line.hs
@@ -0,0 +1,279 @@
+-- |
+-- Module      : Fuyu.GPIO.Line
+-- Description : High-level operations for GPIO line settings, requests, and value I/O.
+-- Maintainer  : BassGT
+-- Stability   : experimental
+-- Portability : POSIX (Linux gpiod v2)
+--
+-- This module provides managed resource brackets ('withSettings', 'withConfig', 'withRequest')
+-- for configuring GPIO line properties (direction, bias, drive mode, active-low, debounce)
+-- and requesting access to read or write logical values to GPIO lines.
+module Fuyu.GPIO.Line
+  ( -- * Types & Patterns
+    Settings
+  , Config
+  , Request
+  , RequestConfig
+  , Offset
+  , pattern Offset
+  , Value
+  , pattern Active
+  , pattern Inactive
+  , pattern ValueError
+  , Direction
+  , pattern DirAsIs
+  , pattern DirInput
+  , pattern DirOutput
+  , Edge
+  , pattern EdgeNone
+  , pattern EdgeRising
+  , pattern EdgeFalling
+  , pattern EdgeBoth
+  , Bias
+  , pattern BiasAsIs
+  , pattern BiasUnknown
+  , pattern BiasDisabled
+  , pattern BiasPullUp
+  , pattern BiasPullDown
+  , Drive
+  , pattern PushPull
+  , pattern OpenDrain
+  , pattern OpenSource
+  , Clock
+  , pattern Monotonic
+  , pattern Realtime
+  , pattern Hardware
+
+    -- * Managed Resource Allocation (with*)
+  , withSettings
+  , withConfig
+  , withRequest
+
+    -- * Line Settings Operations
+  , setDirection
+  , direction
+  , setEdgeDetection
+  , edgeDetection
+  , setBias
+  , bias
+  , setDrive
+  , drive
+  , setEventClock
+  , eventClock
+  , setActiveLow
+  , activeLow
+  , setDebouncePeriodUs
+  , debouncePeriodUs
+  , setOutputValue
+  , outputValue
+  , resetSettings
+
+    -- * Line Configuration Operations
+  , addSettings
+  , settings
+  , setOutputValues
+  , numOffsets
+  , configuredOffsets
+  , resetConfig
+
+    -- * Line Value Operations (Read / Write)
+  , value
+  , values
+  , valuesSubset
+  , setValue
+  , setValues
+  , setValuesSubset
+
+    -- * Line Request Operations & Metadata
+  , chipName
+  , numLines
+  , requestedOffsets
+  , fd
+  , reconfigureLines
+  ) where
+
+import Control.Exception (bracket, throwIO)
+import Data.ByteString (ByteString)
+import qualified Data.Vector.Storable as V
+import System.Posix.Types (Fd)
+import qualified Fuyu.GPIO.Direct as D
+import Fuyu.GPIO.Exception
+import Fuyu.GPIO.Line.Unsafe (newSettings, freeSettings, newConfig, freeConfig, requestLines, releaseRequest)
+import Fuyu.GPIO.Types
+
+--------------------------------------------------------------------------------
+-- Resource Bracket Management
+--------------------------------------------------------------------------------
+
+-- | Allocate a new line settings object and free it automatically afterwards.
+withSettings :: (Settings -> IO a) -> IO a
+withSettings = bracket newSettings freeSettings
+
+-- | Allocate a new line configuration object and free it automatically afterwards.
+withConfig :: (Config -> IO a) -> IO a
+withConfig = bracket newConfig freeConfig
+
+-- | Request GPIO lines from a chip and automatically release them afterwards.
+withRequest :: Chip -> Maybe RequestConfig -> Config -> (Request -> IO a) -> IO a
+withRequest chip maybeReqConf lineConf = bracket (requestLines chip maybeReqConf lineConf) releaseRequest
+
+--------------------------------------------------------------------------------
+-- Line Settings Setters & Getters
+--------------------------------------------------------------------------------
+
+-- | Set the line direction in the settings.
+setDirection :: Settings -> Direction -> IO ()
+setDirection set dir = unwrapOrThrow LineSettingsSetFailed (D.lineSettingsSetDirection set dir)
+
+-- | Get the line direction from the settings.
+direction :: Settings -> IO Direction
+direction = D.lineSettingsDirection
+
+-- | Set edge detection in the settings.
+setEdgeDetection :: Settings -> Edge -> IO ()
+setEdgeDetection set edge = unwrapOrThrow LineSettingsSetFailed (D.lineSettingsSetEdgeDetection set edge)
+
+-- | Get edge detection from the settings.
+edgeDetection :: Settings -> IO Edge
+edgeDetection = D.lineSettingsEdgeDetection
+
+-- | Set electrical bias in the settings.
+setBias :: Settings -> Bias -> IO ()
+setBias _ BiasUnknown = throwIO $ InvalidArgument "setBias: BiasUnknown is a read-only state and cannot be set as a bias configuration."
+setBias set biasVal      = unwrapOrThrow LineSettingsSetFailed (D.lineSettingsSetBias set biasVal)
+
+-- | Get electrical bias from the settings.
+bias :: Settings -> IO Bias
+bias = D.lineSettingsBias
+
+-- | Set drive mode in the settings.
+setDrive :: Settings -> Drive -> IO ()
+setDrive set driveMode = unwrapOrThrow LineSettingsSetFailed (D.lineSettingsSetDrive set driveMode)
+
+-- | Get drive mode from the settings.
+drive :: Settings -> IO Drive
+drive = D.lineSettingsDrive
+
+-- | Set event clock source in the settings.
+setEventClock :: Settings -> Clock -> IO ()
+setEventClock set clk = unwrapOrThrow LineSettingsSetFailed (D.lineSettingsSetEventClock set clk)
+
+-- | Get event clock source from the settings.
+eventClock :: Settings -> IO Clock
+eventClock = D.lineSettingsEventClock
+
+-- | Set active-low in the settings.
+setActiveLow :: Settings -> Bool -> IO ()
+setActiveLow = D.lineSettingsSetActiveLow
+
+-- | Get active-low setting.
+activeLow :: Settings -> IO Bool
+activeLow = D.lineSettingsActiveLow
+
+-- | Set debounce period in microseconds.
+setDebouncePeriodUs :: Settings -> Word -> IO ()
+setDebouncePeriodUs = D.lineSettingsSetDebouncePeriodUs
+
+-- | Get debounce period in microseconds.
+debouncePeriodUs :: Settings -> IO Word
+debouncePeriodUs = D.lineSettingsDebouncePeriodUs
+
+-- | Set default output value in the settings.
+setOutputValue :: Settings -> Value -> IO ()
+setOutputValue _ ValueError = throwIO $ InvalidArgument "setOutputValue: ValueError pattern is a read-only error state and cannot be set as an output value."
+setOutputValue set val = unwrapOrThrow LineSettingsSetFailed (D.lineSettingsSetOutputValue set val)
+
+-- | Get default output value from the settings.
+outputValue :: Settings -> IO Value
+outputValue = D.lineSettingsOutputValue
+
+-- | Reset line settings object to default values.
+resetSettings :: Settings -> IO ()
+resetSettings = D.lineSettingsReset
+
+--------------------------------------------------------------------------------
+-- Line Configuration Operations
+--------------------------------------------------------------------------------
+
+-- | Add settings for a vector of line offsets in the configuration.
+addSettings :: Config -> V.Vector Offset -> Settings -> IO ()
+addSettings config offsets stgs = unwrapOrThrow LineConfigNewFailed (D.lineConfigAddLineSettings config offsets stgs)
+
+-- | Get settings for a specific line offset from configuration.
+settings :: Config -> Offset -> IO Settings
+settings config offset' = unwrapOrThrow LineConfigNewFailed (D.lineConfigLineSettings config offset')
+
+-- | Set output values for lines in configuration.
+setOutputValues :: Config -> V.Vector Value -> IO ()
+setOutputValues config vals
+  | V.elem ValueError vals = throwIO $ InvalidArgument "setOutputValues: Vector contains ValueError pattern, which cannot be set as an output value."
+  | otherwise              = unwrapOrThrow LineConfigNewFailed (D.lineConfigSetOutputValues config vals)
+
+-- | Get the number of configured offsets in the line configuration.
+numOffsets :: Config -> IO Word
+numOffsets = D.lineConfigNumOffsets
+
+-- | Get all configured line offsets in the configuration as a Storable 'V.Vector'.
+configuredOffsets :: Config -> IO (V.Vector Offset)
+configuredOffsets = D.lineConfigConfiguredOffsets
+
+-- | Reset line configuration object to empty state.
+resetConfig :: Config -> IO ()
+resetConfig = D.lineConfigReset
+
+--------------------------------------------------------------------------------
+-- Line Reading & Writing
+--------------------------------------------------------------------------------
+
+-- | Get the logical value of a requested GPIO line at the given offset.
+value :: Request -> Offset -> IO Value
+value req offset' = unwrapOrThrow LineValueReadFailed (D.lineRequestValue req offset')
+
+-- | Get the logical values of all requested lines as a Storable 'V.Vector'.
+values :: Request -> IO (V.Vector Value)
+values req = unwrapOrThrow LineValueReadFailed (D.lineRequestValues req)
+
+-- | Get the logical values of a subset of requested lines specified by offsets.
+valuesSubset :: Request -> V.Vector Offset -> IO (V.Vector Value)
+valuesSubset req offsets = unwrapOrThrow LineValueReadFailed (D.lineRequestSubsetValues req offsets)
+
+-- | Set the logical value of a requested GPIO line at the given offset.
+setValue :: Request -> Offset -> Value -> IO ()
+setValue _ _ ValueError  = throwIO $ InvalidArgument "setValue: ValueError pattern is a read-only error state and cannot be written to a GPIO line."
+setValue req offset' val = unwrapOrThrow LineValueWriteFailed (D.lineRequestSetValue req offset' val)
+
+-- | Set the logical values of all requested lines from a Storable 'V.Vector'.
+setValues :: Request -> V.Vector Value -> IO ()
+setValues req vals
+  | V.elem ValueError vals = throwIO $ InvalidArgument "setValues: Vector contains ValueError pattern, which cannot be written to GPIO lines."
+  | otherwise              = unwrapOrThrow LineValueWriteFailed (D.lineRequestSetValues req vals)
+
+-- | Set the logical values of a subset of requested lines from vectors of offsets and values.
+setValuesSubset :: Request -> V.Vector Offset -> V.Vector Value -> IO ()
+setValuesSubset req offsets vals
+  | V.elem ValueError vals = throwIO $ InvalidArgument "setValuesSubset: Vector contains ValueError pattern, which cannot be written to GPIO lines."
+  | otherwise              = unwrapOrThrow LineValueWriteFailed (D.lineRequestSetValuesSubset req offsets vals)
+
+--------------------------------------------------------------------------------
+-- Line Request Operations & Metadata
+--------------------------------------------------------------------------------
+
+-- | Get the name of the chip this request was made on.
+chipName :: Request -> IO ByteString
+chipName = D.lineRequestChipName
+
+-- | Get the number of lines in the request.
+numLines :: Request -> IO Word
+numLines = D.lineRequestNumLines
+
+-- | Get all requested line offsets as a Storable 'V.Vector'.
+requestedOffsets :: Request -> IO (V.Vector Offset)
+requestedOffsets = D.lineRequestRequestedOffsets
+
+-- | Get the file descriptor associated with the line request handle.
+fd :: Request -> IO Fd
+fd = D.lineRequestFd
+
+-- | Update the configuration of lines associated with an active line request.
+reconfigureLines :: Request -> Config -> IO ()
+reconfigureLines req config = unwrapOrThrow LineReconfigureFailed (D.lineRequestReconfigure req config)
diff --git a/src/Fuyu/GPIO/Line/Info.hs b/src/Fuyu/GPIO/Line/Info.hs
new file mode 100644
--- /dev/null
+++ b/src/Fuyu/GPIO/Line/Info.hs
@@ -0,0 +1,92 @@
+-- |
+-- Module      : Fuyu.GPIO.Line.Info
+-- Description : Read-only metadata query functions for LineInfo snapshots.
+-- Maintainer  : BassGT
+-- Stability   : experimental
+-- Portability : POSIX (Linux gpiod v2)
+--
+-- This module provides functions to inspect 'LineInfo' snapshots.
+-- It is designed to be imported qualified:
+--
+-- @
+-- import qualified Fuyu.GPIO.Line.Info as LineInfo
+-- @
+module Fuyu.GPIO.Line.Info
+  ( -- * Types
+    LineInfo
+
+    -- * Managed Resource Allocation
+  , withLineInfo
+
+    -- * Metadata Accessors
+  , offset
+  , name
+  , isUsed
+  , consumer
+  , direction
+  , edgeDetection
+  , bias
+  , drive
+  , isActiveLow
+  , isDebounced
+  , debouncePeriod
+  , eventClock
+  ) where
+
+import Control.Exception (bracket)
+import Data.ByteString (ByteString)
+import qualified Fuyu.GPIO.Direct as D
+import Fuyu.GPIO.Line.Info.Unsafe (lineInfo, freeLineInfo)
+import Fuyu.GPIO.Types
+
+-- | Retrieve information about a specific line on a chip and free it automatically afterwards.
+withLineInfo :: Chip -> Offset -> (LineInfo -> IO a) -> IO a
+withLineInfo chip offset' = bracket (lineInfo chip offset') freeLineInfo
+
+-- | Get the numeric 'Offset' of the line from a 'LineInfo' snapshot.
+offset :: LineInfo -> IO Offset
+offset = D.lineInfoOffset
+
+-- | Get the name of the line (e.g. "GPIO17"), if set.
+name :: LineInfo -> IO (Maybe ByteString)
+name = D.lineInfoName
+
+-- | Check if the line is currently in use by a consumer kernel driver or user process.
+isUsed :: LineInfo -> IO Bool
+isUsed = D.lineInfoIsUsed
+
+-- | Get the consumer name string of the line, if in use.
+consumer :: LineInfo -> IO (Maybe ByteString)
+consumer = D.lineInfoConsumer
+
+-- | Get the configured direction of the line ('DirInput', 'DirOutput', 'DirAsIs').
+direction :: LineInfo -> IO Direction
+direction = D.lineInfoDirection
+
+-- | Get the configured edge detection of the line ('EdgeNone', 'EdgeRising', 'EdgeFalling', 'EdgeBoth').
+edgeDetection :: LineInfo -> IO Edge
+edgeDetection = D.lineInfoEdgeDetection
+
+-- | Get the configured electrical bias ('BiasDisabled', 'BiasPullUp', 'BiasPullDown', etc.).
+bias :: LineInfo -> IO Bias
+bias = D.lineInfoBias
+
+-- | Get the configured drive mode ('PushPull', 'OpenDrain', 'OpenSource').
+drive :: LineInfo -> IO Drive
+drive = D.lineInfoDrive
+
+-- | Check if active-low logic is configured for the line.
+isActiveLow :: LineInfo -> IO Bool
+isActiveLow = D.lineInfoIsActiveLow
+
+-- | Check if hardware debounce is configured for the line.
+isDebounced :: LineInfo -> IO Bool
+isDebounced = D.lineInfoIsDebounced
+
+-- | Get the debounce period in microseconds for the line.
+debouncePeriod :: LineInfo -> IO Word
+debouncePeriod = D.lineInfoDebouncePeriod
+
+-- | Get the event clock source configured for the line ('Monotonic', 'Realtime', 'Hardware').
+eventClock :: LineInfo -> IO Clock
+eventClock = D.lineInfoEventClock
diff --git a/src/Fuyu/GPIO/Line/Info/Unsafe.hs b/src/Fuyu/GPIO/Line/Info/Unsafe.hs
new file mode 100644
--- /dev/null
+++ b/src/Fuyu/GPIO/Line/Info/Unsafe.hs
@@ -0,0 +1,35 @@
+-- |
+-- Module      : Fuyu.GPIO.Line.Info.Unsafe
+-- Description : Unsafe manual resource allocation for LineInfo snapshots.
+-- Maintainer  : BassGT
+-- Stability   : experimental
+-- Portability : POSIX (Linux gpiod v2)
+--
+-- Manual resource allocation ('lineInfo', 'freeLineInfo', 'copyLineInfo') for 'LineInfo' handles.
+module Fuyu.GPIO.Line.Info.Unsafe
+  ( -- * Types
+    LineInfo
+
+    -- * Unsafe Manual Resource Allocation
+  , lineInfo
+  , freeLineInfo
+  , copyLineInfo
+  ) where
+
+import qualified Fuyu.GPIO.Direct as D
+import Fuyu.GPIO.Exception
+import Fuyu.GPIO.Types
+
+-- | Retrieve information about a specific line on a chip.
+-- Must be manually freed using 'freeLineInfo'.
+lineInfo :: Chip -> Offset -> IO LineInfo
+lineInfo chip offset' = unwrapOrThrow LineInfoFailed (D.chipLineInfo chip offset')
+
+-- | Free a 'LineInfo' handle.
+freeLineInfo :: LineInfo -> IO ()
+freeLineInfo = D.lineInfoFree
+
+-- | Make a copy of a 'LineInfo' snapshot.
+-- Must be manually freed using 'freeLineInfo'.
+copyLineInfo :: LineInfo -> IO LineInfo
+copyLineInfo info = unwrapOrThrow LineInfoCopyFailed (D.lineInfoCopy info)
diff --git a/src/Fuyu/GPIO/Line/Unsafe.hs b/src/Fuyu/GPIO/Line/Unsafe.hs
new file mode 100644
--- /dev/null
+++ b/src/Fuyu/GPIO/Line/Unsafe.hs
@@ -0,0 +1,57 @@
+-- |
+-- Module      : Fuyu.GPIO.Line.Unsafe
+-- Description : Unsafe manual resource allocation for line settings, config, and requests.
+-- Maintainer  : BassGT
+-- Stability   : experimental
+-- Portability : POSIX (Linux gpiod v2)
+--
+-- Manual resource allocation ('newSettings', 'freeSettings', 'newConfig', 'freeConfig',
+-- 'requestLines', 'releaseRequest') for line settings, configs, and requests.
+module Fuyu.GPIO.Line.Unsafe
+  ( -- * Types
+    Chip
+  , Settings
+  , Config
+  , Request
+  , RequestConfig
+
+    -- * Unsafe Manual Resource Allocation
+  , newSettings
+  , freeSettings
+  , newConfig
+  , freeConfig
+  , requestLines
+  , releaseRequest
+  ) where
+
+import qualified Fuyu.GPIO.Direct as D
+import Fuyu.GPIO.Exception
+import Fuyu.GPIO.Types
+
+-- | Allocate a new line settings object.
+-- Must be manually freed with 'freeSettings'.
+newSettings :: IO Settings
+newSettings = unwrapOrThrow LineSettingsNewFailed D.lineSettingsNew
+
+-- | Free a line settings object.
+freeSettings :: Settings -> IO ()
+freeSettings = D.lineSettingsFree
+
+-- | Allocate a new line configuration object.
+-- Must be manually freed with 'freeConfig'.
+newConfig :: IO Config
+newConfig = unwrapOrThrow LineConfigNewFailed D.lineConfigNew
+
+-- | Free a line configuration object.
+freeConfig :: Config -> IO ()
+freeConfig = D.lineConfigFree
+
+-- | Request GPIO lines from a chip.
+-- Must be manually released with 'releaseRequest'.
+requestLines :: Chip -> Maybe RequestConfig -> Config -> IO Request
+requestLines chip maybeReqConf lineConf =
+  unwrapOrThrow LineRequestFailed (D.chipRequestLines chip maybeReqConf lineConf)
+
+-- | Release a line request handle.
+releaseRequest :: Request -> IO ()
+releaseRequest = D.lineRequestRelease
diff --git a/src/Fuyu/GPIO/RequestConfig.hs b/src/Fuyu/GPIO/RequestConfig.hs
new file mode 100644
--- /dev/null
+++ b/src/Fuyu/GPIO/RequestConfig.hs
@@ -0,0 +1,43 @@
+-- |
+-- Module      : Fuyu.GPIO.RequestConfig
+-- Description : Managed resource operations for line request configurations.
+-- Maintainer  : BassGT
+-- Stability   : experimental
+-- Portability : POSIX (Linux gpiod v2)
+--
+-- Managed configuration options (consumer name, kernel event buffer size) for line requests.
+module Fuyu.GPIO.RequestConfig
+  ( RequestConfig
+  , KernelBufferSize
+  , withRequestConfig
+  , setConsumer
+  , consumer
+  , setBufferSize
+  , bufferSize
+  ) where
+
+import Control.Exception (bracket)
+import Data.ByteString (ByteString)
+import qualified Fuyu.GPIO.Direct as D
+import Fuyu.GPIO.RequestConfig.Unsafe (newRequestConfig, freeRequestConfig)
+import Fuyu.GPIO.Types
+
+-- | Allocate a new request configuration object and free it automatically afterwards.
+withRequestConfig :: (RequestConfig -> IO a) -> IO a
+withRequestConfig = bracket newRequestConfig freeRequestConfig
+
+-- | Set consumer name for request config.
+setConsumer :: RequestConfig -> ByteString -> IO ()
+setConsumer = D.requestConfigSetConsumer
+
+-- | Get consumer name from request config.
+consumer :: RequestConfig -> IO ByteString
+consumer = D.requestConfigConsumer
+
+-- | Set kernel event buffer size for request config. Pass 0 to use kernel default (64).
+setBufferSize :: RequestConfig -> KernelBufferSize -> IO ()
+setBufferSize = D.requestConfigSetEventBufferSize
+
+-- | Get kernel event buffer size from request config.
+bufferSize :: RequestConfig -> IO KernelBufferSize
+bufferSize = D.requestConfigEventBufferSize
diff --git a/src/Fuyu/GPIO/RequestConfig/Unsafe.hs b/src/Fuyu/GPIO/RequestConfig/Unsafe.hs
new file mode 100644
--- /dev/null
+++ b/src/Fuyu/GPIO/RequestConfig/Unsafe.hs
@@ -0,0 +1,29 @@
+-- |
+-- Module      : Fuyu.GPIO.RequestConfig.Unsafe
+-- Description : Unsafe manual resource allocation for RequestConfig.
+-- Maintainer  : BassGT
+-- Stability   : experimental
+-- Portability : POSIX (Linux gpiod v2)
+--
+-- Manual resource allocation ('newRequestConfig', 'freeRequestConfig') for 'RequestConfig' handles.
+module Fuyu.GPIO.RequestConfig.Unsafe
+  ( -- * Types
+    RequestConfig
+
+    -- * Unsafe Manual Resource Allocation
+  , newRequestConfig
+  , freeRequestConfig
+  ) where
+
+import qualified Fuyu.GPIO.Direct as D
+import Fuyu.GPIO.Exception
+import Fuyu.GPIO.Types
+
+-- | Allocate a new request configuration object.
+-- Must be manually freed with 'freeRequestConfig'.
+newRequestConfig :: IO RequestConfig
+newRequestConfig = unwrapOrThrow RequestConfigNewFailed D.requestConfigNew
+
+-- | Free a request configuration object.
+freeRequestConfig :: RequestConfig -> IO ()
+freeRequestConfig = D.requestConfigFree
diff --git a/src/Fuyu/GPIO/Types.hs b/src/Fuyu/GPIO/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/Fuyu/GPIO/Types.hs
@@ -0,0 +1,299 @@
+{-# LANGUAGE PatternSynonyms #-}
+module Fuyu.GPIO.Types
+  ( -- * Handles & Opaque Objects
+    Chip
+  , ChipInfo
+  , LineInfo
+  , Settings
+  , Config
+  , Request
+  , RequestConfig
+  , Buffer
+  , Event
+  , ReadyRequest(..)
+  , readyToRequest
+  , ReadyChip(..)
+  , readyToChip
+
+    -- * Units & Index Types
+  , Offset
+  , pattern Offset
+  , Capacity
+  , userBufferCapacity
+  , capacity
+  , KernelBufferSize
+  , Timeout
+  , pattern Nanoseconds
+  , pattern Immediate
+  , pattern Infinite
+  , Timestamp
+  , WaitResult(..)
+  , EdgeEvent(..)
+
+    -- * Line Values & Patterns
+  , Value
+  , pattern Active
+  , pattern Inactive
+  , pattern ValueError
+
+    -- * Line Direction & Patterns
+  , Direction
+  , pattern DirAsIs
+  , pattern DirInput
+  , pattern DirOutput
+
+    -- * Edge Detection & Patterns
+  , Edge
+  , pattern EdgeNone
+  , pattern EdgeRising
+  , pattern EdgeFalling
+  , pattern EdgeBoth
+
+    -- * Electrical Bias & Patterns
+  , Bias
+  , pattern BiasAsIs
+  , pattern BiasUnknown
+  , pattern BiasDisabled
+  , pattern BiasPullUp
+  , pattern BiasPullDown
+
+    -- * Drive Mode & Patterns
+  , Drive
+  , pattern PushPull
+  , pattern OpenDrain
+  , pattern OpenSource
+
+    -- * Event Clock & Patterns
+  , Clock
+  , pattern Monotonic
+  , pattern Realtime
+  , pattern Hardware
+
+    -- * Edge Event Types & Patterns
+  , EdgeEventType
+  , pattern Rising
+  , pattern Falling
+
+    -- * Line Info Event Types & Patterns
+  , InfoEvent
+  , InfoEventType
+  , pattern Requested
+  , pattern Released
+  , pattern ConfigChanged
+  ) where
+
+import Foreign.C.Types (CUInt, CULong)
+import qualified Fuyu.GPIO.Direct as D
+
+-- | Alias for 'D.Chip'
+type Chip          = D.Chip
+-- | Alias for 'D.ChipInfo'
+type ChipInfo      = D.ChipInfo
+-- | Alias for 'D.LineInfo'
+type LineInfo      = D.LineInfo
+-- | Alias for 'D.LineSettings'
+type Settings      = D.LineSettings
+-- | Alias for 'D.LineConfig'
+type Config        = D.LineConfig
+-- | Alias for 'D.Request'
+type Request       = D.LineRequest
+-- | Alias for 'D.RequestConfig'
+type RequestConfig = D.RequestConfig
+-- | Alias for 'D.EventBuffer'
+type Buffer        = D.EventBuffer
+-- | Alias for 'D.RawEdgeEvent'
+type Event         = D.RawEdgeEvent
+
+-- | Alias for 'D.LineOffset'
+type Offset        = D.LineOffset
+-- | Alias for 'D.TimeoutNs'
+type Timeout       = D.TimeoutNs
+-- | Alias for 'D.TimestampNs'
+type Timestamp     = D.TimestampNs
+-- | Alias for 'D.LineValue'
+type Value         = D.LineValue
+-- | Alias for 'D.LineDirection'
+type Direction     = D.LineDirection
+-- | Alias for 'D.LineEdge'
+type Edge          = D.LineEdge
+-- | Alias for 'D.LineBias'
+type Bias          = D.LineBias
+-- | Alias for 'D.LineDrive'
+type Drive         = D.LineDrive
+-- | Alias for 'D.LineClock'
+type Clock         = D.LineClock
+-- | Alias for 'D.EdgeEventType'
+type EdgeEventType = D.EdgeEventType
+-- | Alias for 'D.InfoEvent'
+type InfoEvent     = D.InfoEvent
+-- | Alias for 'D.InfoEventType'
+type InfoEventType = D.InfoEventType
+
+-- | Security token wrapping a 'Request' that has been confirmed ready by 'Fuyu.GPIO.EdgeEvent.waitEvents'.
+newtype ReadyRequest = ReadyRequest Request
+  deriving (Eq, Show)
+
+-- | Extract the underlying 'Request' from a 'ReadyRequest'.
+readyToRequest :: ReadyRequest -> Request
+readyToRequest (ReadyRequest req) = req
+
+-- | Security token wrapping a 'Chip' that has been confirmed ready by 'Fuyu.GPIO.Chip.Watch.waitEvent'.
+newtype ReadyChip = ReadyChip Chip
+  deriving (Eq, Show)
+
+-- | Extract the underlying 'Chip' from a 'ReadyChip'.
+readyToChip :: ReadyChip -> Chip
+readyToChip (ReadyChip chip) = chip
+
+-- | Result of waiting for events on a line request or watched chip handle.
+data WaitResult a
+  = EventReady !a
+  | TimeoutResult
+  deriving (Eq, Show)
+
+-- | Pure Haskell representation of a parsed edge detection event.
+data EdgeEvent = EdgeEvent
+  { eventLineOffset :: !Offset
+  , eventType       :: !EdgeEventType
+  , eventTimestamp  :: !Timestamp
+  } deriving (Eq, Ord, Show, Read)
+
+-- | Pattern constructor for 'Offset'.
+pattern Offset :: CUInt -> Offset 
+pattern Offset n = D.LineOffset n
+
+-- | Capacity for the user-space edge event buffer (in number of events).
+newtype Capacity = Capacity Word
+  deriving (Eq, Ord, Show, Read)
+
+-- | Smart constructor for user-space event buffer 'Capacity'.
+-- Automatically clamps capacity between 1 and 1024 (where 0 defaults to 64 per libgpiod specifications).
+userBufferCapacity :: Word -> Capacity
+userBufferCapacity n
+  | n == 0    = Capacity 64
+  | n > 1024  = Capacity 1024
+  | otherwise = Capacity n
+
+-- | Extract the numeric capacity value from a 'Capacity' handle.
+capacity :: Capacity -> Word
+capacity (Capacity n) = n
+
+-- | Size of the kernel-level event ring-buffer (in number of events).
+-- Pass 0 to use the kernel default size (64).
+type KernelBufferSize = Word
+
+-- | Pattern constructor for 'Timeout' in nanoseconds.
+pattern Nanoseconds :: CULong -> Timeout
+pattern Nanoseconds n = D.Nanoseconds n
+
+-- | Immediate (non-blocking) timeout value.
+pattern Immediate :: Timeout
+pattern Immediate = D.Immediate
+
+-- | Infinite (blocking) timeout value.
+pattern Infinite :: Timeout
+pattern Infinite = D.Infinite
+
+-- | Active line logical state (high/active).
+pattern Active :: Value
+pattern Active = D.LineActive
+
+-- | Inactive line logical state (low/inactive).
+pattern Inactive :: Value
+pattern Inactive = D.LineInactive
+
+-- | Error line state.
+pattern ValueError :: Value
+pattern ValueError = D.LineError
+
+-- | Leave direction configuration as is.
+pattern DirAsIs :: Direction
+pattern DirAsIs = D.DirAsIs
+
+-- | Configure line direction as input.
+pattern DirInput :: Direction
+pattern DirInput = D.DirInput
+
+-- | Configure line direction as output.
+pattern DirOutput :: Direction
+pattern DirOutput = D.DirOutput
+
+-- | Disable edge detection.
+pattern EdgeNone :: Edge
+pattern EdgeNone = D.EdgeNone
+
+-- | Detect rising edge transitions.
+pattern EdgeRising :: Edge
+pattern EdgeRising = D.EdgeRising
+
+-- | Detect falling edge transitions.
+pattern EdgeFalling :: Edge
+pattern EdgeFalling = D.EdgeFalling
+
+-- | Detect both rising and falling edge transitions.
+pattern EdgeBoth :: Edge
+pattern EdgeBoth = D.EdgeBoth
+
+-- | Leave bias configuration as is.
+pattern BiasAsIs :: Bias
+pattern BiasAsIs = D.BiasAsIs
+
+-- | Unknown electrical bias.
+pattern BiasUnknown :: Bias
+pattern BiasUnknown = D.BiasUnknown
+
+-- | Disable electrical bias (floating).
+pattern BiasDisabled :: Bias
+pattern BiasDisabled = D.BiasDisabled
+
+-- | Enable internal pull-up resistor.
+pattern BiasPullUp :: Bias
+pattern BiasPullUp = D.BiasPullUp
+
+-- | Enable internal pull-down resistor.
+pattern BiasPullDown :: Bias
+pattern BiasPullDown = D.BiasPullDown
+
+-- | Push-pull drive mode.
+pattern PushPull :: Drive
+pattern PushPull = D.PushPull
+
+-- | Open-drain drive mode.
+pattern OpenDrain :: Drive
+pattern OpenDrain = D.OpenDrain
+
+-- | Open-source drive mode.
+pattern OpenSource :: Drive
+pattern OpenSource = D.OpenSource
+
+-- | Monotonic clock source for event timestamps.
+pattern Monotonic :: Clock
+pattern Monotonic = D.Monotonic
+
+-- | Realtime clock source for event timestamps.
+pattern Realtime :: Clock
+pattern Realtime = D.Realtime
+
+-- | Hardware clock source for event timestamps.
+pattern Hardware :: Clock
+pattern Hardware = D.Hardware
+
+-- | Rising edge event type.
+pattern Rising :: EdgeEventType
+pattern Rising = D.Rising
+
+-- | Falling edge event type.
+pattern Falling :: EdgeEventType
+pattern Falling = D.Falling
+
+-- | Line requested info event type.
+pattern Requested :: InfoEventType
+pattern Requested = D.LineRequested
+
+-- | Line released info event type.
+pattern Released :: InfoEventType
+pattern Released = D.LineReleased
+
+-- | Line configuration changed info event type.
+pattern ConfigChanged :: InfoEventType
+pattern ConfigChanged = D.LineConfigChanged
