diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
 # Revision history for pms-infra-procspawn
 
+## 0.0.9.0 -- 2026-04-30
+
+* Add async read/write tools.
+
 ## 0.0.8.0 -- 2025-12-31
 
 * Add file system tools.
diff --git a/pms-infra-procspawn.cabal b/pms-infra-procspawn.cabal
--- a/pms-infra-procspawn.cabal
+++ b/pms-infra-procspawn.cabal
@@ -20,7 +20,7 @@
 -- PVP summary:     +-+------- breaking API changes
 --                  | | +----- non-breaking API additions
 --                  | | | +--- code changes with no API change
-version:            0.0.8.0
+version:            0.0.9.0
 
 -- A short (one-line) description of the package.
 synopsis:           pms-infra-procspawn
@@ -90,6 +90,7 @@
                       filepath,
                       aeson,
                       directory,
+                      strip-ansi-escape,
                       pms-domain-model
 
     -- Directories containing source files.
diff --git a/src/PMS/Infra/ProcSpawn/DM/Constant.hs b/src/PMS/Infra/ProcSpawn/DM/Constant.hs
--- a/src/PMS/Infra/ProcSpawn/DM/Constant.hs
+++ b/src/PMS/Infra/ProcSpawn/DM/Constant.hs
@@ -6,4 +6,9 @@
 _LOG_FILE_NAME :: String
 _LOG_FILE_NAME = "pms-infra-procspawn.log"
 
+-- |
+--
+_READ_BUFFER_SIZE :: Int
+_READ_BUFFER_SIZE = 4096
+
 
diff --git a/src/PMS/Infra/ProcSpawn/DM/Type.hs b/src/PMS/Infra/ProcSpawn/DM/Type.hs
--- a/src/PMS/Infra/ProcSpawn/DM/Type.hs
+++ b/src/PMS/Infra/ProcSpawn/DM/Type.hs
@@ -101,3 +101,19 @@
   def = ProcStringArrayToolParams {
         _argumentsProcStringArrayToolParams = def
       }
+
+-- |
+--
+data ProcIntToolParams =
+  ProcIntToolParams {
+    _argumentsProcIntToolParams :: Int
+  } deriving (Show, Read, Eq)
+
+$(deriveJSON defaultOptions {fieldLabelModifier = DM.dropDataName "ProcIntToolParams", omitNothingFields = True} ''ProcIntToolParams)
+makeLenses ''ProcIntToolParams
+
+instance Default ProcIntToolParams where
+  def = ProcIntToolParams {
+        _argumentsProcIntToolParams = def
+      }
+
diff --git a/src/PMS/Infra/ProcSpawn/DS/Core.hs b/src/PMS/Infra/ProcSpawn/DS/Core.hs
--- a/src/PMS/Infra/ProcSpawn/DS/Core.hs
+++ b/src/PMS/Infra/ProcSpawn/DS/Core.hs
@@ -1,6 +1,7 @@
 {-# LANGUAGE TemplateHaskell #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE FlexibleContexts #-}
 
 module PMS.Infra.ProcSpawn.DS.Core where
 
@@ -20,6 +21,9 @@
 import qualified Control.Exception.Safe as E
 import System.Exit
 import qualified Data.Text.Encoding as TE
+import qualified Data.String.AnsiEscapeCodes.Strip.Text as ANSI
+import qualified Data.Text.Encoding.Error as TEE
+
 import qualified Data.ByteString as BS
 import Data.Aeson 
 import Data.List
@@ -32,6 +36,7 @@
 import qualified PMS.Domain.Model.DM.Constant as DM
 
 import PMS.Infra.ProcSpawn.DM.Type
+import qualified PMS.Infra.ProcSpawn.DM.Constant as DM_CONST
 import PMS.Infra.ProcSpawn.DS.Utility
 
 
@@ -83,6 +88,8 @@
     go (DM.ProcRunCommand dat)       = genProcRunTask dat
     go (DM.ProcTerminateCommand dat) = genProcTerminateTask dat
     go (DM.ProcMessageCommand dat)   = genProcMessageTask dat
+    go (DM.ProcReadCommand dat)  = genProcReadTask dat
+    go (DM.ProcWriteCommand dat) = genProcWriteTask dat
 
 ---------------------------------------------------------------------------------
 -- |
@@ -155,9 +162,10 @@
 genProcSpawn cmdDat = do
   let name     = cmdDat^.DM.nameProcRunCommandData
       argsBS   = DM.unRawJsonByteString $ cmdDat^.DM.argumentsProcRunCommandData
-      tout     = DM._TIMEOUT_MICROSEC
       addEnv   = [("ProgramData", "C:\\ProgramData"), ("SystemRoot", "C:\\Windows")] -- for windows ssh on claude code.
 
+  tout <- view DM.timeoutMicrosecDomainData <$> lift ask
+
   prompts   <- view DM.promptsDomainData <$> lift ask
   resQ      <- view DM.responseQueueDomainData <$> lift ask
   invalidChars <- view DM.invalidCharsDomainData <$> lift ask
@@ -239,7 +247,7 @@
 
   STM.atomically (STM.readTMVar procVar) >>= \case
     Just p -> do
-      race (DM.expect lockTMVar (readProc p) prompts) (CC.threadDelay tout) >>= \case
+      race (DM.expect lockTMVar (readProc p tout DM_CONST._READ_BUFFER_SIZE) prompts) (CC.threadDelay tout) >>= \case
         Left res -> toolsCallResponse resQ (cmdDat^.DM.jsonrpcProcRunCommandData) ExitSuccess (maybe "Nothing" id res) ""
         Right _  -> toolsCallResponse resQ (cmdDat^.DM.jsonrpcProcRunCommandData) (ExitFailure 1) "" "timeout occurred."
     Nothing -> do
@@ -262,7 +270,7 @@
 genProcCMD cmdDat = do
   $logDebugS DM._LOGTAG $ T.pack $ "genProcCMD: called. "
 
-  let tout = DM._TIMEOUT_MICROSEC
+  tout <- view DM.timeoutMicrosecDomainData <$> lift ask
 
   prompts <- view DM.promptsDomainData <$> lift ask
   resQ <- view DM.responseQueueDomainData <$> lift ask
@@ -304,7 +312,7 @@
       hFlush wHdl
 
   STM.atomically (STM.readTMVar procVar) >>= \case
-    Just p -> race (DM.expect lockTMVar (readProc p) prompts) (CC.threadDelay tout) >>= \case
+    Just p -> race (DM.expect lockTMVar (readProc p tout DM_CONST._READ_BUFFER_SIZE) prompts) (CC.threadDelay tout) >>= \case
       Left res -> toolsCallResponse resQ (cmdDat^.DM.jsonrpcProcRunCommandData) ExitSuccess (maybe "Nothing" id res) ""
       Right _  -> toolsCallResponse resQ (cmdDat^.DM.jsonrpcProcRunCommandData) (ExitFailure 1) "" "timeout occurred."
     Nothing -> do
@@ -327,7 +335,7 @@
 genProcPS cmdDat = do
   $logDebugS DM._LOGTAG $ T.pack $ "genProcPS: called. "
 
-  let tout = DM._TIMEOUT_MICROSEC
+  tout <- view DM.timeoutMicrosecDomainData <$> lift ask
 
   prompts <- view DM.promptsDomainData <$> lift ask
   resQ <- view DM.responseQueueDomainData <$> lift ask
@@ -357,7 +365,7 @@
     Nothing -> runProc procVar "powershell" ["-NoLogo", "-NoExit", "-Command", initCmd] env
 
   STM.atomically (STM.readTMVar procVar) >>= \case
-    Just p -> race (DM.expect lockTMVar (readProc p) prompts) (CC.threadDelay tout) >>= \case
+    Just p -> race (DM.expect lockTMVar (readProc p tout DM_CONST._READ_BUFFER_SIZE) prompts) (CC.threadDelay tout) >>= \case
       Left res -> toolsCallResponse resQ (cmdDat^.DM.jsonrpcProcRunCommandData) ExitSuccess (maybe "Nothing" id res) ""
       Right _  -> toolsCallResponse resQ (cmdDat^.DM.jsonrpcProcRunCommandData) (ExitFailure 1) "" "timeout occurred."
     Nothing -> do
@@ -422,10 +430,89 @@
 
 -- |
 --
+genProcReadTask :: DM.ProcReadCommandData -> AppContext (IOTask ())
+genProcReadTask cmdData = do
+  resQ <- view DM.responseQueueDomainData <$> lift ask
+  procTMVar <- view processAppData <$> ask
+  tout <- view DM.timeoutMicrosecDomainData <$> lift ask
+
+  let argsBS = DM.unRawJsonByteString $ cmdData^.DM.argumentsProcReadCommandData
+  argsDat <- liftEither $ eitherDecode $ argsBS
+  let size = argsDat^.argumentsProcIntToolParams
+      actualSize = min size DM_CONST._READ_BUFFER_SIZE
+
+  return $ procReadTask cmdData resQ procTMVar tout actualSize
+
+-- |
+--
+procReadTask :: DM.ProcReadCommandData
+             -> STM.TQueue DM.McpResponse
+             -> STM.TMVar (Maybe ProcData)
+             -> Int
+             -> Int
+             -> IOTask ()
+procReadTask cmdDat resQ procTMVar tout actualSize = flip E.catchAny errHdl $ do
+  hPutStrLn stderr $ "[INFO] PMS.Infra.ProcSpawn.DS.Core.procReadTask run. actualSize: " ++ show actualSize
+  
+  STM.atomically (STM.readTMVar procTMVar) >>= \case
+    Nothing -> do
+      hPutStrLn stderr "[ERROR] PMS.Infra.ProcSpawn.DS.Core.procReadTask: process is not started."
+      toolsCallResponse resQ jsonRpc (ExitFailure 1) "" "process is not started."
+    Just p -> do
+      output <- readProc p tout actualSize 
+      let result = T.unpack $ ANSI.stripAnsiEscapeCodes $ TE.decodeUtf8With TEE.lenientDecode output
+      toolsCallResponse resQ jsonRpc ExitSuccess result ""
+
+  where
+    jsonRpc = cmdDat^.DM.jsonrpcProcReadCommandData
+    errHdl e = toolsCallResponse resQ jsonRpc (ExitFailure 1) "" (show e)
+
+-- |
+--
+genProcWriteTask :: DM.ProcWriteCommandData -> AppContext (IOTask ())
+genProcWriteTask cmdData = do
+  let argsBS = DM.unRawJsonByteString $ cmdData^.DM.argumentsProcWriteCommandData
+  resQ <- view DM.responseQueueDomainData <$> lift ask
+  procTMVar <- view processAppData <$> ask
+  invalidChars <- view DM.invalidCharsDomainData <$> lift ask
+  invalidCmds <- view DM.invalidCmdsDomainData <$> lift ask
+  argsDat <- liftEither $ eitherDecode $ argsBS
+  let args = argsDat^.argumentsProcStringToolParams
+  return $ procWriteTask cmdData resQ procTMVar args invalidChars invalidCmds
+
+-- |
+--
+procWriteTask :: DM.ProcWriteCommandData
+                   -> STM.TQueue DM.McpResponse
+                   -> STM.TMVar (Maybe ProcData)
+                   -> String
+                   -> [String]
+                   -> [String]
+                   -> IOTask ()
+procWriteTask cmdDat resQ procTMVar args invalidChars invalidCmds = flip E.catchAny errHdl $ do
+  hPutStrLn stderr $ "[INFO] PMS.Infra.ProcSpawn.DS.Core.procWriteTask run. " ++ args
+  
+  STM.atomically (STM.readTMVar procTMVar) >>= \case
+    Nothing -> do
+      hPutStrLn stderr "[ERROR] PMS.Infra.ProcSpawn.DS.Core.procWriteTask: process is not started."
+      toolsCallResponse resQ jsonRpc (ExitFailure 1) "" "process is not started."
+    Just p -> do
+      let wHdl = p^.wHdLProcData
+      msg <- DM.validateMessage invalidChars invalidCmds args
+      let cmd = TE.encodeUtf8 $ T.pack $ msg ++ DM._LF
+      BS.hPut wHdl cmd
+      hFlush wHdl
+      toolsCallResponse resQ jsonRpc ExitSuccess "success" ""
+
+  where
+    jsonRpc = cmdDat^.DM.jsonrpcProcWriteCommandData
+    errHdl e = toolsCallResponse resQ jsonRpc (ExitFailure 1) "" (show e)
+-- |
+--
 genProcMessageTask :: DM.ProcMessageCommandData -> AppContext (IOTask ())
 genProcMessageTask cmdData = do
   let argsBS = DM.unRawJsonByteString $ cmdData^.DM.argumentsProcMessageCommandData
-      tout = 30 * 1000 * 1000
+  tout <- view DM.timeoutMicrosecDomainData <$> lift ask
   prompts <- view DM.promptsDomainData <$> lift ask
   resQ <- view DM.responseQueueDomainData <$> lift ask
   invalidChars <- view DM.invalidCharsDomainData <$> lift ask
@@ -480,6 +567,6 @@
       BS.hPut wHdl cmd
       hFlush wHdl
 
-      race (DM.expect lockTMVar (readProc pDat) prompts) (CC.threadDelay tout) >>= \case
+      race (DM.expect lockTMVar (readProc pDat tout DM_CONST._READ_BUFFER_SIZE) prompts) (CC.threadDelay tout) >>= \case
         Left res -> toolsCallResponse resQ jsonRpc ExitSuccess (maybe "expect running. skip." id res) ""
         Right _  -> toolsCallResponse resQ jsonRpc (ExitFailure 1) "" "timeout occurred."
diff --git a/src/PMS/Infra/ProcSpawn/DS/Utility.hs b/src/PMS/Infra/ProcSpawn/DS/Utility.hs
--- a/src/PMS/Infra/ProcSpawn/DS/Utility.hs
+++ b/src/PMS/Infra/ProcSpawn/DS/Utility.hs
@@ -15,8 +15,6 @@
 import qualified System.Process as S
 import qualified Data.ByteString as BS
 import qualified System.Environment as Env
-import qualified Data.ByteString.Char8 as BSC
-import Control.Concurrent.Async
 
 import qualified PMS.Domain.Model.DM.Type as DM
 import qualified PMS.Domain.Model.DS.Utility as DM
@@ -91,6 +89,11 @@
   (fromProcHandle, toPtyHandle) <- S.createPipe
   -- (fromProcEHandle, toPtyEHandle) <- S.createPipe
 
+  -- hSetBuffering toProcHandle NoBuffering
+  -- hSetBuffering fromProcHandle NoBuffering
+  -- hSetBuffering fromPtyHandle NoBuffering
+  -- hSetBuffering toPtyHandle NoBuffering
+
   baseEnv <- Env.getEnvironment
   let cwd = Nothing
       runEnvs = Just $ baseEnv ++ addEnv
@@ -113,73 +116,14 @@
 
   hPutStrLn stderr "[INFO] PMS.Infra.ProcSpawn.DS.Core.procRunTask.runProc end."
 
-
 -- |
 --   
-readProc :: ProcData -> IO BS.ByteString
-readProc dat = do
+readProc :: ProcData -> Int -> Int -> IO BS.ByteString
+readProc dat tout size = do
   let hdl = dat^.rHdlProcData
-      timeoutMillis = 25000
-  ready <- hWaitForInput hdl timeoutMillis
+  ready <- hWaitForInput hdl tout
   if ready
-    then BS.hGetSome hdl 1024
-    else E.throwString $ "readProc: timeout waiting " ++ show timeoutMillis ++ " for input"
-
-
--- |
---   
-readProc2 :: ProcData -> IO BS.ByteString
-readProc2 dat = do
-  let rHdl = dat^.rHdlProcData
-      eHdl = dat^.eHdlProcData
-
-  readProcHandle eHdl 1000 >>= \case
-    Just eout -> readProcHandle rHdl 500 >>= \case
-      Just erout -> do
-        hPutStrLn stderr "[INFO] PMS.Infra.ProcSpawn.DS.Core.readProc eout,erout."
-        return $ erout <> BSC.pack "\n" <> eout
-      Nothing    -> do
-        hPutStrLn stderr "[INFO] PMS.Infra.ProcSpawn.DS.Core.readProc eout,none."
-        return eout
-    Nothing   -> readProcHandle rHdl 20000 >>= \case
-      Just rout -> readProcHandle eHdl 500 >>= \case
-          Just reout -> do
-            hPutStrLn stderr "[INFO] PMS.Infra.ProcSpawn.DS.Core.readProc none,rout,reout."
-            return $ rout <> BSC.pack "\n" <> reout
-          Nothing    -> do
-            hPutStrLn stderr "[INFO] PMS.Infra.ProcSpawn.DS.Core.readProc none,rout,none."
-            return rout
-      Nothing   -> readProcHandle eHdl 500 >>= \case
-          Just out -> do
-            hPutStrLn stderr "[INFO] PMS.Infra.ProcSpawn.DS.Core.readProc none,none,out"
-            return out
-          Nothing  -> E.throwString $ "readProc: timeout waiting for stdout/stderr input"
-
-
--- |
---   
-readProc3 :: ProcData -> IO BS.ByteString
-readProc3 dat = do
-  let rHdl = dat^.rHdlProcData
-      eHdl = dat^.eHdlProcData
-
-  hPutStrLn stderr "[INFO] PMS.Infra.ProcSpawn.DS.Core.readProc start race."
-  res <- race (readProcHandle rHdl 25001)
-              (readProcHandle eHdl 25002)
-
-  hPutStrLn stderr "[INFO] PMS.Infra.ProcSpawn.DS.Core.readProc end race."
-  case res of
-    Left (Just out)  -> return out
-    Right (Just err) -> return err
-    _                -> E.throwString "readProc: timeout waiting for stdout/stderr input"
-
+    then BS.hGetSome hdl size
+    else E.throwString $ "readProc: timeout occurred. (waited " ++ show tout ++ ".)"
 
--- |
---   
-readProcHandle :: Handle -> Int -> IO (Maybe BS.ByteString)
-readProcHandle hdl timeoutMillis = do
-  ready <- hWaitForInput hdl timeoutMillis
-  if ready
-    then Just <$> BS.hGetSome hdl 8192
-    else return Nothing
 
