diff --git a/driver/net/core/src/config.rs b/driver/net/core/src/config.rs
index 8865d547..f5e66a59 100644
--- a/driver/net/core/src/config.rs
+++ b/driver/net/core/src/config.rs
@@ -3,7 +3,7 @@ use serde::Serialize;
 use vfs::{ChannelDescriptor, MessagePayload};
 use yggdrasil_abi::{
     error::Error,
-    io::MessageDestination,
+    io::{ChannelPublisherId, MessageDestination},
     net::{
         netconfig::{
             InterfaceInfo, InterfaceQuery, NetConfigRequest, NetConfigResult, RouteInfo,
@@ -18,7 +18,9 @@ use crate::{
     l3::{arp, Route},
 };
 
-async fn receive_request(channel: &ChannelDescriptor) -> Result<(u32, NetConfigRequest), Error> {
+async fn receive_request(
+    channel: &ChannelDescriptor,
+) -> Result<(ChannelPublisherId, NetConfigRequest), Error> {
     loop {
         let raw = channel.receive_message_async().await?;
         match &raw.payload {
@@ -35,13 +37,13 @@ async fn receive_request(channel: &ChannelDescriptor) -> Result<(u32, NetConfigR
 
 fn send_reply<T: Serialize>(
     channel: &ChannelDescriptor,
-    recepient: u32,
+    recepient: ChannelPublisherId,
     message: NetConfigResult<T>,
 ) -> Result<(), Error> {
     let data = serde_json::to_vec(&message).map_err(|_| Error::InvalidArgument)?;
     channel.send_message(
         MessagePayload::Data(data.into_boxed_slice()),
-        MessageDestination::Specific(recepient),
+        MessageDestination::Specific(recepient.into()),
     )
 }
 
@@ -81,7 +83,7 @@ fn query_route(destination: IpAddr) -> Option<RoutingInfo> {
 
     Some(RoutingInfo {
         interface_name: interface.name.clone(),
-        interface_id: interface_id,
+        interface_id,
         destination,
         gateway,
         source,
diff --git a/lib/vfs/src/channel.rs b/lib/vfs/src/channel.rs
index 28b19f49..0feb212d 100644
--- a/lib/vfs/src/channel.rs
+++ b/lib/vfs/src/channel.rs
@@ -13,7 +13,10 @@ use alloc::{
 use futures_util::{task::AtomicWaker, Future};
 use libk_thread::{block, sync::Mutex};
 use libk_util::sync::{IrqSafeSpinlock, LockMethod};
-use yggdrasil_abi::{error::Error, io::MessageDestination};
+use yggdrasil_abi::{
+    error::Error,
+    io::{ChannelPublisherId, MessageDestination},
+};
 
 use crate::{FileReadiness, FileRef};
 
@@ -34,7 +37,7 @@ pub enum MessagePayload {
 /// Describes a message sent over a channel
 pub struct Message {
     /// Channel descriptor ID from which the message came
-    pub source: u32,
+    pub source: ChannelPublisherId,
     /// Data of the message
     pub payload: MessagePayload,
 }
@@ -89,7 +92,7 @@ impl ChannelDescriptor {
         dst: MessageDestination,
     ) -> Result<(), Error> {
         let message = Arc::new(Message {
-            source: self.id,
+            source: unsafe { ChannelPublisherId::from_raw(self.id) },
             payload,
         });
 
diff --git a/lib/vfs/src/pty.rs b/lib/vfs/src/pty.rs
index 8f3dd662..597360a5 100644
--- a/lib/vfs/src/pty.rs
+++ b/lib/vfs/src/pty.rs
@@ -19,7 +19,7 @@ use yggdrasil_abi::{
         DeviceRequest, TerminalInputOptions, TerminalLineOptions, TerminalOptions,
         TerminalOutputOptions, TerminalSize,
     },
-    process::Signal,
+    process::{ProcessId, Signal},
 };
 
 const CAPACITY: usize = 8192;
@@ -38,7 +38,7 @@ struct PtyMasterToSlaveHalf {
     // Actual data to be read by the slave
     buffer: IrqSafeSpinlock<MasterToSlaveBuffer>,
     ready_ring: LossyRingQueue<u8>,
-    signal_pgroup: IrqSafeRwLock<Option<u32>>,
+    signal_pgroup: IrqSafeRwLock<Option<ProcessId>>,
 }
 
 /// Pseudo-terminal shared device
diff --git a/libk/libk-thread/src/lib.rs b/libk/libk-thread/src/lib.rs
index 6360d83e..fe3c5503 100644
--- a/libk/libk-thread/src/lib.rs
+++ b/libk/libk-thread/src/lib.rs
@@ -16,10 +16,10 @@ use kernel_arch::{Architecture, ArchitectureImpl, KernelTableManagerImpl};
 use libk_mm::phys::GlobalPhysicalAllocator;
 
 pub(crate) mod api {
-    use yggdrasil_abi::process::Signal;
+    use yggdrasil_abi::process::{ProcessId, Signal};
 
     extern "Rust" {
-        pub fn __signal_process_group(group_id: u32, signal: Signal);
+        pub fn __signal_process_group(group_id: ProcessId, signal: Signal);
     }
 }
 
@@ -41,7 +41,7 @@ pub type TaskContextImpl =
 use sched::CpuQueue;
 pub use types::{AtomicThreadState, ThreadAffinity, ThreadId, ThreadState};
 
-use yggdrasil_abi::process::Signal;
+use yggdrasil_abi::process::{ProcessId, Signal};
 
 /// Returns local CPU index
 #[inline]
@@ -53,6 +53,6 @@ pub fn cpu_count() -> usize {
     ArchitectureImpl::cpu_count()
 }
 
-pub fn signal_process_group(group_id: u32, signal: Signal) {
+pub fn signal_process_group(group_id: ProcessId, signal: Signal) {
     unsafe { __signal_process_group(group_id, signal) }
 }
diff --git a/libk/libk-thread/src/process.rs b/libk/libk-thread/src/process.rs
index fab3a36f..4b041253 100644
--- a/libk/libk-thread/src/process.rs
+++ b/libk/libk-thread/src/process.rs
@@ -19,13 +19,13 @@ use libk_util::{
 };
 use yggdrasil_abi::{
     error::Error,
-    process::{ExitCode, Signal, ThreadSpawnOptions},
+    process::{ExitCode, ProcessId, Signal, ThreadSpawnOptions},
 };
 
 use crate::{
     futex::UserspaceMutex,
     thread::Thread,
-    types::{ProcessId, ProcessTlsInfo},
+    types::{AllocateProcessId, ProcessTlsInfo},
     TaskContextImpl, ThreadId,
 };
 
@@ -146,7 +146,7 @@ impl<PM: ProcessManager<Process = Self>, IO: ProcessIo> ProcessImpl<PM, IO> {
         image: Option<ProcessImage>,
     ) -> (Arc<Self>, Arc<Thread>) {
         let name = name.into();
-        let id = ProcessId::next();
+        let id = ProcessId::new();
 
         let process = Arc::new(Self {
             name,
@@ -248,10 +248,7 @@ impl<PM: ProcessManager<Process = Self>, IO: ProcessIo> ProcessImpl<PM, IO> {
         let src_thread = Thread::current();
         let src_process = src_thread.process::<PM>();
 
-        let value = src_process
-            .fork_inner(frame)
-            .map(|ProcessId(p)| p as u32)
-            .into_syscall_register();
+        let value = src_process.fork_inner(frame).into_syscall_register();
 
         frame.set_return_value(value as _);
     }
diff --git a/libk/libk-thread/src/thread.rs b/libk/libk-thread/src/thread.rs
index 8157386c..8d259b87 100644
--- a/libk/libk-thread/src/thread.rs
+++ b/libk/libk-thread/src/thread.rs
@@ -18,14 +18,14 @@ use libk_util::{
 };
 use yggdrasil_abi::{
     error::Error,
-    process::{ExitCode, Signal, SignalEntryData},
+    process::{ExitCode, ProcessId, Signal, SignalEntryData},
 };
 
 use crate::{
     mem::ForeignPointer,
     process::{Process, ProcessManager},
     sched::CpuQueue,
-    types::{ProcessId, ThreadAffinity, ThreadId, ThreadState},
+    types::{ThreadAffinity, ThreadId, ThreadState},
     TaskContextImpl,
 };
 
diff --git a/libk/libk-thread/src/types.rs b/libk/libk-thread/src/types.rs
index 1cc3c2cb..f07d62e5 100644
--- a/libk/libk-thread/src/types.rs
+++ b/libk/libk-thread/src/types.rs
@@ -1,10 +1,15 @@
 use core::{
     fmt,
     mem::size_of,
-    sync::atomic::{AtomicU64, Ordering},
+    sync::atomic::{AtomicU32, AtomicU64, Ordering},
 };
 
 use atomic_enum::atomic_enum;
+use yggdrasil_abi::process::ProcessId;
+
+pub trait AllocateProcessId {
+    fn new() -> Self;
+}
 
 /// Represents the states a thread can be at some point in time
 #[atomic_enum]
@@ -34,11 +39,6 @@ pub enum ThreadId {
     User(u64),
 }
 
-/// Unique number assigned to each [Process]
-#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
-#[repr(transparent)]
-pub struct ProcessId(pub u64);
-
 // TLS layout (x86-64):
 // |     mem_size     | uthread_size |
 // |   Data    .......|   self, ???  |
@@ -145,31 +145,11 @@ impl fmt::Display for ThreadId {
     }
 }
 
-impl fmt::Display for ProcessId {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "<Process {}>", self.0)
-    }
-}
-
-// XXX TODO Remove this
-impl From<ProcessId> for u32 {
-    fn from(value: ProcessId) -> Self {
-        value.0 as _
-    }
-}
-
-impl From<u32> for ProcessId {
-    fn from(value: u32) -> Self {
-        Self(value as _)
-    }
-}
-
-impl ProcessId {
-    /// Generates a new [ProcessId]
-    pub fn next() -> Self {
-        static COUNTER: AtomicU64 = AtomicU64::new(1);
+impl AllocateProcessId for ProcessId {
+    fn new() -> Self {
+        static COUNTER: AtomicU32 = AtomicU32::new(1);
         let id = COUNTER.fetch_add(1, Ordering::SeqCst);
-        Self(id)
+        unsafe { Self::from_raw(id) }
     }
 }
 
diff --git a/src/device/display/linear_fb.rs b/src/device/display/linear_fb.rs
index f72b51eb..6cb50184 100644
--- a/src/device/display/linear_fb.rs
+++ b/src/device/display/linear_fb.rs
@@ -5,7 +5,7 @@ use core::{
     task::{Context, Poll},
 };
 
-use abi::{error::Error, io::DeviceRequest};
+use abi::{error::Error, io::DeviceRequest, process::ProcessId};
 use device_api::Device;
 use libk_mm::{
     address::{IntoRaw, PhysicalAddress},
@@ -17,7 +17,7 @@ use libk_thread::thread::Thread;
 use libk_util::sync::IrqSafeSpinlock;
 use ygg_driver_block::BlockDevice;
 
-use crate::{arch::L3, task::process::ProcessId};
+use crate::arch::L3;
 
 use super::{DisplayDevice, DisplayDimensions};
 
diff --git a/src/device/serial/pl011.rs b/src/device/serial/pl011.rs
index 061d77b3..08f830cd 100644
--- a/src/device/serial/pl011.rs
+++ b/src/device/serial/pl011.rs
@@ -1,5 +1,5 @@
 //! ARM PL011 driver
-use abi::{error::Error, io::DeviceRequest};
+use abi::{error::Error, io::DeviceRequest, process::ProcessId};
 use alloc::boxed::Box;
 use device_api::{
     interrupt::{InterruptHandler, Irq},
@@ -25,7 +25,6 @@ use vfs::{CharDevice, FileReadiness};
 use crate::{
     debug::{self, DebugSink, LogLevel},
     device::tty::{TtyContext, TtyDevice},
-    task::process::ProcessId,
 };
 
 register_bitfields! {
diff --git a/src/device/tty.rs b/src/device/tty.rs
index bb279942..c213adc3 100644
--- a/src/device/tty.rs
+++ b/src/device/tty.rs
@@ -8,17 +8,14 @@ use core::{
 use abi::{
     error::Error,
     io::{TerminalInputOptions, TerminalLineOptions, TerminalOptions, TerminalOutputOptions},
-    process::Signal,
+    process::{ProcessId, Signal},
 };
 use device_api::serial::SerialDevice;
 use futures_util::Future;
 use libk_thread::process::ProcessImpl;
 use libk_util::{ring::RingBuffer, sync::IrqSafeSpinlock, waker::QueueWaker};
 
-use crate::{
-    proc::io::ProcessIoImpl,
-    task::process::{ProcessId, ProcessManagerImpl},
-};
+use crate::{proc::io::ProcessIoImpl, task::process::ProcessManagerImpl};
 
 struct TerminalRing {
     buffer: IrqSafeSpinlock<RingBuffer<u8>>,
diff --git a/src/syscall/mod.rs b/src/syscall/mod.rs
index 592a0000..4ba2eb5c 100644
--- a/src/syscall/mod.rs
+++ b/src/syscall/mod.rs
@@ -39,7 +39,6 @@ fn run_with_io_at<
 }
 
 mod impls {
-    use abi::process::{ExecveOptions, SpawnOption};
     pub(crate) use abi::{
         error::Error,
         io::{
@@ -53,6 +52,10 @@ mod impls {
         process::{ExitCode, MutexOperation, Signal, SignalEntryData, SpawnOptions},
         system::SystemInfo,
     };
+    use abi::{
+        io::ChannelPublisherId,
+        process::{ExecveOptions, ProcessId, SpawnOption},
+    };
     use alloc::{boxed::Box, sync::Arc};
     use libk::{block, runtime};
     use vfs::{File, IoContext, MessagePayload, Read, Seek, Write};
@@ -71,7 +74,6 @@ mod impls {
     use libk_thread::{
         process::{Process, ProcessManager},
         thread::Thread,
-        types::ProcessId,
     };
 
     use crate::{
@@ -178,7 +180,7 @@ mod impls {
         thread.exit_process::<ProcessManagerImpl>(code)
     }
 
-    pub(crate) fn spawn_process(options: &SpawnOptions<'_>) -> Result<u32, Error> {
+    pub(crate) fn spawn_process(options: &SpawnOptions<'_>) -> Result<ProcessId, Error> {
         let thread = Thread::current();
         let process = thread.process::<ProcessManagerImpl>();
 
@@ -191,7 +193,7 @@ mod impls {
                 options.arguments,
                 options.environment,
             )?;
-            let pid: u32 = child_process.id().into();
+            let pid = child_process.id();
 
             // Inherit group and session from the creator
             child_process.inherit(&process)?;
@@ -210,10 +212,10 @@ mod impls {
                         }
                     }
                     &SpawnOption::SetProcessGroup(pgroup) => {
-                        let pgroup = if pgroup == 0 {
+                        let pgroup = if pgroup.into_raw() == 0 {
                             child_process.id()
                         } else {
-                            pgroup.into()
+                            pgroup
                         };
                         child_process.set_group_id(pgroup);
                     }
@@ -247,17 +249,16 @@ mod impls {
         })
     }
 
-    pub(crate) fn wait_process(pid: u32, status: &mut ExitCode) -> Result<(), Error> {
-        let pid = ProcessId::from(pid);
+    pub(crate) fn wait_process(pid: ProcessId, status: &mut ExitCode) -> Result<(), Error> {
         let target = ProcessManagerImpl::get(pid).ok_or(Error::DoesNotExist)?;
         *status = block!(target.wait_for_exit().await)?;
         Ok(())
     }
 
-    pub(crate) fn get_pid() -> u32 {
+    pub(crate) fn get_pid() -> ProcessId {
         let thread = Thread::current();
         let process = thread.process::<ProcessManagerImpl>();
-        process.id().into()
+        process.id()
     }
 
     pub(crate) fn nanosleep(duration: &Duration) {
@@ -272,8 +273,7 @@ mod impls {
         thread.set_signal_entry(ip, sp);
     }
 
-    pub(crate) fn send_signal(target: u32, signal: Signal) -> Result<(), Error> {
-        let pid = ProcessId(target as _);
+    pub(crate) fn send_signal(pid: ProcessId, signal: Signal) -> Result<(), Error> {
         let target = ProcessManagerImpl::get(pid).ok_or(Error::DoesNotExist)?;
         target.raise_signal(signal);
         Ok(())
@@ -660,7 +660,7 @@ mod impls {
         fd: RawFd,
         metadata: &mut MaybeUninit<ReceivedMessageMetadata>,
         buf: &mut [u8],
-        from: &mut MaybeUninit<u32>,
+        from: &mut MaybeUninit<ChannelPublisherId>,
     ) -> Result<(), Error> {
         let thread = Thread::current();
         let process = thread.process::<ProcessManagerImpl>();
@@ -823,7 +823,7 @@ mod impls {
         unreachable!()
     }
 
-    pub(crate) fn fork() -> Result<u32, Error> {
+    pub(crate) fn fork() -> Result<ProcessId, Error> {
         unreachable!()
     }
 
@@ -835,7 +835,7 @@ mod impls {
 mod generated {
     #![allow(unreachable_code)]
 
-    use abi::SyscallFunction;
+    use abi::{io::ChannelPublisherId, process::ProcessId, SyscallFunction};
     use abi_lib::SyscallRegister;
 
     use super::{
diff --git a/src/task/process.rs b/src/task/process.rs
index e9015583..d069fe5b 100644
--- a/src/task/process.rs
+++ b/src/task/process.rs
@@ -1,6 +1,6 @@
 //! Process data structures
 
-use abi::process::Signal;
+use abi::process::{ProcessId, Signal};
 use alloc::{collections::BTreeMap, sync::Arc};
 use libk_thread::process::{Process, ProcessManager};
 use libk_util::sync::spin_rwlock::IrqSafeRwLock;
@@ -9,7 +9,7 @@ use crate::proc::io::ProcessIoImpl;
 
 pub use libk_thread::{
     process::ProcessImage,
-    types::{ProcessId, ProcessTlsInfo, ProcessTlsLayout},
+    types::{ProcessTlsInfo, ProcessTlsLayout},
 };
 
 /// Process manager implementation
@@ -40,6 +40,6 @@ impl ProcessManager for ProcessManagerImpl {
 }
 
 #[no_mangle]
-fn __signal_process_group(group_id: u32, signal: Signal) {
-    ProcessImpl::signal_group(ProcessId(group_id as _), signal)
+fn __signal_process_group(group_id: ProcessId, signal: Signal) {
+    ProcessImpl::signal_group(group_id, signal)
 }