diff --git a/PublicDomain b/PublicDomain
new file mode 100644
--- /dev/null
+++ b/PublicDomain
@@ -0,0 +1,24 @@
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to <http://unlicense.org>
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,36 @@
+### raketka
+
+    basic distributed-process node
+    
+    configurable peers
+
+    Begin reading at Control.Distributed.Raketka.Type.Arg
+
+    see also 
+    
+    [parconc-examples](https://hackage.haskell.org/package/parconc-examples)
+    
+    [haskell-distributed tutorials](http://haskell-distributed.github.io/tutorials/1ch.html)  
+    
+    This library has basic working code 
+    to enable to start nodes and to connect them with each other. 
+    
+    The library may be extended 
+    however you are most likely to write your own code that does much more. 
+    
+    This library is simple on purpose. 
+    It took me a few days to write this code and make it work.    
+    
+    To start the program that comes with this library:
+    
+    start the same program in multiple consoles: 1 per node
+     
+    pass 2 args:
+    
+        1. path to config.json (see enclosed test-conf.json)
+        2. idx of this node in the cluster: 0 - (length Cluster -1)
+    
+    The program exchanges pings with other nodes which are expected to pong back. 
+    Both pings & pongs are output to stdout
+    
+    When you shut one node, the other nodes stdout notifications about that node     
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/changelog.md b/changelog.md
new file mode 100644
--- /dev/null
+++ b/changelog.md
@@ -0,0 +1,4 @@
+#####  1.0
+    start multiple nodes
+    
+    on start nodes ping suggested (in config) nodes - which pong back - and establish monitored connections
diff --git a/raketka.cabal b/raketka.cabal
new file mode 100644
--- /dev/null
+++ b/raketka.cabal
@@ -0,0 +1,139 @@
+name:                raketka
+version:             1.0
+build-type:          Simple
+synopsis:            basic distributed-process node with configurable peers
+description:         start multiple nodes, let them communicate. 
+                     
+author:              Imants Cekusins
+maintainer:          Imants Cekusins
+category:            Network Control
+license:             PublicDomain
+license-file:        PublicDomain
+extra-source-files:  changelog.md, README.md, test-conf.json
+cabal-version:       >=1.10
+homepage:            https://github.com/ciez/raketka
+source-repository   head
+   type: git
+   location: https://github.com/ciez/raketka.git
+
+
+library
+  exposed-modules:
+          Control.Distributed.Raketka.NodeId
+          Control.Distributed.Raketka.Type.Arg
+          Control.Distributed.Raketka.HandleMsg
+          Control.Distributed.Raketka.NewServerInfo
+          Control.Distributed.Raketka.Process.Send
+          Control.Distributed.Raketka.Type.Server
+          Control.Distributed.Raketka.Type.Message
+          Control.Distributed.Raketka.Process.Server
+          Control.Distributed.Raketka.Master
+
+  ghc-options:  -fwarn-unused-imports  
+    
+  build-depends:       base >=4.8 && <5.0,
+                       binary, 
+                       containers, 
+                       stm,
+                       distributed-process,
+                       distributed-process-simplelocalnet,
+                       tagged,
+                       random,
+                       async,
+                       network,
+                       network-transport,
+                       network-transport-tcp,
+                       aeson,
+                       conf-json,
+                       template-haskell
+
+                         
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+  default-extensions:   FlexibleInstances
+                        MultiParamTypeClasses
+                        TypeSynonymInstances
+                        BangPatterns
+                        InstanceSigs
+                        OverloadedStrings
+                        FunctionalDependencies
+                        StandaloneDeriving
+                        DeriveGeneric
+                        DeriveDataTypeable
+                        RecordWildCards
+                        ScopedTypeVariables
+                        TemplateHaskell
+                        FlexibleContexts
+                        TypeFamilies
+                        ConstraintKinds
+                        RankNTypes
+                       
+                       
+executable raketka
+  main-is:             Main.hs
+  ghc-options:  -fwarn-unused-imports         
+  other-modules:      Control.Distributed.Raketka.Impl.LByteString
+                      Control.Distributed.Raketka.Impl.Inst
+
+  build-depends:       base >=4.8 && <5.0,
+                       binary,
+                       bytestring,
+                       containers, 
+                       stm,
+                       tagged,
+                       distributed-process,
+                       distributed-process-simplelocalnet,
+                       async,
+                       random,
+                       network,
+                       network-transport,
+                       network-transport-tcp,
+                       aeson,
+                       conf-json,
+                       template-haskell
+
+                        
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+  default-extensions:   FlexibleInstances
+                        MultiParamTypeClasses
+                        TypeSynonymInstances
+                        BangPatterns
+                        InstanceSigs
+                        OverloadedStrings
+                        FunctionalDependencies
+                        StandaloneDeriving
+                        DeriveGeneric
+                        DeriveDataTypeable
+                        RecordWildCards
+                        ScopedTypeVariables
+                        TemplateHaskell
+                        FlexibleContexts
+                        TypeFamilies
+                        ConstraintKinds
+                        RankNTypes
+                        GeneralizedNewtypeDeriving
+
+                        
+test-suite spec
+  default-language:Haskell2010
+  type: exitcode-stdio-1.0
+  ghc-options:  -fwarn-unused-imports  
+  hs-source-dirs: test, src
+  default-extensions:   FlexibleInstances
+                        MultiParamTypeClasses
+                        TypeSynonymInstances
+                        BangPatterns
+                        OverloadedStrings
+                        FunctionalDependencies
+                        StandaloneDeriving
+                        ScopedTypeVariables
+                        TemplateHaskell
+                        ConstraintKinds
+
+
+  main-is: Main.hs
+
+  build-depends:  base >= 4.8,
+                  hspec >= 2.1.7,
+                  QuickCheck >= 2.8.1                      
diff --git a/src/Control/Distributed/Raketka/HandleMsg.hs b/src/Control/Distributed/Raketka/HandleMsg.hs
new file mode 100644
--- /dev/null
+++ b/src/Control/Distributed/Raketka/HandleMsg.hs
@@ -0,0 +1,47 @@
+module Control.Distributed.Raketka.HandleMsg where
+
+import Data.Tagged
+import Text.Printf
+import Control.Distributed.Process as P hiding (Message, handleMessage)
+import Control.Concurrent.STM
+import Control.Distributed.Raketka.NewServerInfo
+import Control.Distributed.Raketka.Process.Send
+import Control.Distributed.Raketka.Type.Server as T
+import Control.Distributed.Raketka.Type.Message as M
+
+{- | both 'Info' with 'Ping' and ordinary 'Messages' 
+
+calls 
+
+    * 'newServerInfo'
+    * 'handleMessage'
+    
+    depending on message type
+-} 
+handleRemoteMessage::Content c =>
+    Tagged c Server -> Message c -> Process ()
+handleRemoteMessage s1@(Tagged Server{..}) msg0 =  
+  case msg0 of
+    Info ping1 pid1 -> newServerInfo s1 ping1 pid1 
+    Message msg1 -> handleMessage s1 msg1
+
+
+{- | replies to 'whereisRemoteAsync' run on init 
+in "Control.Distributed.Raketka.Master" -}
+handleWhereIsReply::Content c =>
+    Tagged c Server -> WhereIsReply -> Process () 
+handleWhereIsReply _ (P.WhereIsReply _ Nothing) = pure ()
+handleWhereIsReply s1@(Tagged Server{..}) (WhereIsReply _ (Just pid0)) =
+  liftIO $ atomically $ 
+    sendRemote s1 pid0 $ Info Ping spid
+
+
+{- | 'ProcessMonitorNotification' e.g. connection lost  -} 
+handleMonitorNotification::Content c =>
+    Tagged c Server -> ProcessMonitorNotification -> Process ()
+handleMonitorNotification
+       (Tagged Server{..}) (ProcessMonitorNotification _ pid0 _) = do
+  say (printf "server on %s dropped connection" (show pid0))
+  liftIO $ atomically $ do
+    old_pids1 <- readTVar servers
+    writeTVar servers (filter (/= pid0) old_pids1)
diff --git a/src/Control/Distributed/Raketka/Impl/Inst.hs b/src/Control/Distributed/Raketka/Impl/Inst.hs
new file mode 100644
--- /dev/null
+++ b/src/Control/Distributed/Raketka/Impl/Inst.hs
@@ -0,0 +1,32 @@
+module Control.Distributed.Raketka.Impl.Inst where
+
+import Data.Data
+import Data.Binary
+import Data.ByteString
+import Data.Tagged
+import Control.Distributed.Process
+import Control.Distributed.Raketka.Type.Arg
+import Control.Distributed.Raketka.Type.Server
+import qualified Control.Distributed.Raketka.Process.Server as S
+import Debug.Trace
+
+
+newtype TByteString = TByteString ByteString deriving (Data, Typeable, Binary, Show)
+
+
+instance Specific TByteString where
+    handleMessage::Tagged TByteString Server -> TByteString -> Process ()
+    handleMessage serv0 msg0 =
+        trace "handleMessage" $ 
+        pure ()  --  todo 
+
+    startServer::Tagged TByteString ServerId -> Process ()
+    startServer (Tagged id0) = server id0
+
+
+server::ServerId -> Process ()
+server id0 = newServer' >>= flip S.server id0
+
+
+newServer'::Process (Tagged TByteString Server)
+newServer' = S.newServer []
diff --git a/src/Control/Distributed/Raketka/Impl/LByteString.hs b/src/Control/Distributed/Raketka/Impl/LByteString.hs
new file mode 100644
--- /dev/null
+++ b/src/Control/Distributed/Raketka/Impl/LByteString.hs
@@ -0,0 +1,12 @@
+module Control.Distributed.Raketka.Impl.LByteString where
+
+import qualified Data.ByteString as B (ByteString,concat)
+import qualified Data.ByteString.Lazy as L (ByteString,toChunks,fromChunks)
+
+
+toStrict::L.ByteString -> B.ByteString
+toStrict = B.concat . L.toChunks
+
+
+toLazy::B.ByteString -> L.ByteString
+toLazy bs = L.fromChunks [bs]
diff --git a/src/Control/Distributed/Raketka/Master.hs b/src/Control/Distributed/Raketka/Master.hs
new file mode 100644
--- /dev/null
+++ b/src/Control/Distributed/Raketka/Master.hs
@@ -0,0 +1,30 @@
+module Control.Distributed.Raketka.Master where
+
+import Data.Tagged
+import Control.Distributed.Process as P
+import Control.Monad
+import Control.Distributed.Process.Backend.SimpleLocalnet
+import Control.Distributed.Raketka.Type.Arg as T
+import Control.Distributed.Raketka.Type.Server
+import Control.Distributed.Raketka.NodeId as N
+
+{-| * registers own pid
+    * calls 'whereisRemoteAsync'
+    * calls 'startServer'  
+-}
+master::Specific c =>
+    Backend -> Cluster -> Tagged c Int -> Process ()
+master backend0 (Cluster ids0) idx1@(Tagged idx0) = do
+  mynode1 <- getSelfNode
+
+  let peers1 = N.nodeId <$> ids0
+      this1 = ids0 !! idx0
+      service1 = T.service this1
+      peers2 = filter (/= mynode1) peers1
+
+  mypid1 <- getSelfPid
+  register service1 mypid1
+
+  forM_ peers2 $ \(peer1::NodeId) -> P.whereisRemoteAsync peer1 service1
+
+  startServer $ passTag idx1 this1
diff --git a/src/Control/Distributed/Raketka/NewServerInfo.hs b/src/Control/Distributed/Raketka/NewServerInfo.hs
new file mode 100644
--- /dev/null
+++ b/src/Control/Distributed/Raketka/NewServerInfo.hs
@@ -0,0 +1,29 @@
+module Control.Distributed.Raketka.NewServerInfo where
+
+import Data.Tagged
+import Control.Distributed.Process
+  hiding (Message, mask, finally, handleMessage)
+import Control.Monad
+import Text.Printf
+import Control.Concurrent.STM
+import Control.Distributed.Raketka.Type.Server
+import Control.Distributed.Raketka.Type.Message
+import Control.Distributed.Raketka.Process.Send
+
+{- | 'Ping' and 'Pong' handler -}
+newServerInfo::Content c =>
+    Tagged c Server 
+    -> Ping 
+    -> ProcessId 
+    -> Process ()
+
+newServerInfo s1@(Tagged server0@Server{..}) ping0 pid0 = do
+  liftIO $ printf "%s received %s from %s\n" (show spid) (show ping0) (show pid0)
+  join $ liftIO $ atomically $ do
+    old_pids1 <- readTVar servers
+    writeTVar servers (pid0 : filter (/= pid0) old_pids1)
+
+    when (ping0 == Ping) $ sendRemote s1 pid0 $ Info Pong spid 
+
+    -- monitor the new server
+    return (when (pid0 `notElem` old_pids1) $ void $ monitor pid0)
diff --git a/src/Control/Distributed/Raketka/NodeId.hs b/src/Control/Distributed/Raketka/NodeId.hs
new file mode 100644
--- /dev/null
+++ b/src/Control/Distributed/Raketka/NodeId.hs
@@ -0,0 +1,12 @@
+module Control.Distributed.Raketka.NodeId where
+
+import Control.Distributed.Process
+import Network.Transport.TCP
+import Control.Distributed.Raketka.Type.Arg
+
+
+-- | this library does not try to discover peers. Config hints nodes to try to connect to
+nodeId::ServerId -> NodeId
+nodeId id0 = NodeId $ encodeEndPointAddress host1 port1 0
+    where host1 = host id0
+          port1 = show $ port id0  
diff --git a/src/Control/Distributed/Raketka/Process/Send.hs b/src/Control/Distributed/Raketka/Process/Send.hs
new file mode 100644
--- /dev/null
+++ b/src/Control/Distributed/Raketka/Process/Send.hs
@@ -0,0 +1,27 @@
+module Control.Distributed.Raketka.Process.Send
+   (sendRemote,
+   sendRemoteAll)  where
+
+import Data.Tagged
+import Control.Concurrent.STM
+import Control.Distributed.Raketka.Type.Server
+import Control.Distributed.Process
+              hiding (Message, mask, finally, handleMessage, proxy)
+import Control.Distributed.Raketka.Type.Message
+
+
+{- | send message to 'ProcessId'. 
+
+Other ways of sending messages are not implemented to keep the code basic.  
+-}
+sendRemote::Content c =>
+    Tagged c Server -> ProcessId -> Message c -> STM ()
+sendRemote (Tagged Server{..}) pid pmsg =
+    writeTChan proxychan (send pid pmsg)
+
+{- | broadcast message to all known peers -}
+sendRemoteAll::Content c =>
+    Tagged c Server -> Message c -> STM ()
+sendRemoteAll s1@(Tagged server0@Server{..}) pmsg0 = do
+    pids1 <- readTVar servers
+    mapM_ (\pid1 -> sendRemote s1 pid1 pmsg0) pids1
diff --git a/src/Control/Distributed/Raketka/Process/Server.hs b/src/Control/Distributed/Raketka/Process/Server.hs
new file mode 100644
--- /dev/null
+++ b/src/Control/Distributed/Raketka/Process/Server.hs
@@ -0,0 +1,57 @@
+module Control.Distributed.Raketka.Process.Server where
+
+import Data.Tagged hiding (proxy)
+import Control.Concurrent.STM
+import Control.Distributed.Raketka.HandleMsg as H
+import Control.Distributed.Process as P hiding (proxy)
+import Control.Distributed.Raketka.Process.Send
+import Control.Distributed.Raketka.Type.Message
+import Control.Distributed.Raketka.Type.Server as S
+import Control.Distributed.Raketka.Type.Arg as T
+import Control.Monad
+import Debug.Trace
+import qualified Data.Map as Map
+
+{- | * init 'proxy'
+    * send out pings
+    * 'receiveWait's for messages       -}
+server::Content c =>
+    Tagged c Server -> ServerId -> Process ()
+server s1@(Tagged server0) id0  = do
+  P.spawnLocal (proxy s1)
+
+  pid1 <- getSelfPid
+  liftIO $ atomically $ sendRemoteAll s1 $ Info Ping pid1
+
+  forever $
+    P.receiveWait
+      [ P.match $ H.handleRemoteMessage s1
+      , P.match $ handleMonitorNotification s1
+      , P.matchIf (\(WhereIsReply l _) -> l == (service id0)) $
+                handleWhereIsReply s1
+      , P.matchAny $
+            liftIO . traceIO . ("matchAny" ++) . show       -- unknown messages
+      ]
+
+
+-- | pipeline for sending msgs
+proxy::Content c =>
+    Tagged c Server -> Process ()
+proxy (Tagged Server{..}) = forever $ join $
+    liftIO $ atomically $ readTChan proxychan
+
+
+-- | init 'Server' store
+newServer::Content c =>
+     [ProcessId] -> Process (Tagged c Server)
+newServer pids0 = do
+  pid1 <- getSelfPid
+  liftIO $ do
+    s1 <- newTVarIO pids0
+    c1 <- newTVarIO Map.empty
+    o1 <- newTChanIO
+    pure $ Tagged Server { 
+            servers = s1, 
+            proxychan = o1, 
+            spid = pid1
+        }
diff --git a/src/Control/Distributed/Raketka/Type/Arg.hs b/src/Control/Distributed/Raketka/Type/Arg.hs
new file mode 100644
--- /dev/null
+++ b/src/Control/Distributed/Raketka/Type/Arg.hs
@@ -0,0 +1,46 @@
+{- | see also 
+
+<https://hackage.haskell.org/package/parconc-examples>
+
+<http://haskell-distributed.github.io/tutorials/1ch.html> 
+
+This library has basic working code to enable to start nodes and to connect them with each other. The library may be extended however you are most likely to write your own code that does much more. This library is simple on purpose. It took me a few days to write this code and make it work.    
+
+To start the program that comes with this library:
+
+start the same program in multiple consoles: 1 per node
+ 
+pass 2 args:
+
+    1. path to config.json (see enclosed test-conf.json)
+    2. idx of this node in the cluster: 0 - (length Cluster -1)
+
+The program exchanges pings with other nodes which are expected to pong back. Both pings & pongs are output to stdout
+
+When you shut one node, the other nodes stdout notifications about that node    
+-}
+module Control.Distributed.Raketka.Type.Arg where
+
+import GHC.Generics
+import Data.Aeson
+import Data.Binary
+
+-- | one node
+data ServerId = ServerId {
+        host::String,       -- ^ e.g. localhost or 127.0.0.1 
+        port::Int,          -- ^ for node communication
+        service::String     -- ^ channel type: choose any string. Use same type for connected nodes or different types for multiple disconnected clusters 
+    } deriving (Show,Generic)
+
+instance Binary ServerId
+
+
+-- | config file structure
+data Cluster = Cluster [ServerId] deriving (Generic,Show)
+
+
+instance FromJSON ServerId
+-- ^ to read conf
+
+instance FromJSON Cluster
+-- ^ to read conf
diff --git a/src/Control/Distributed/Raketka/Type/Message.hs b/src/Control/Distributed/Raketka/Type/Message.hs
new file mode 100644
--- /dev/null
+++ b/src/Control/Distributed/Raketka/Type/Message.hs
@@ -0,0 +1,16 @@
+module Control.Distributed.Raketka.Type.Message where
+
+import Control.Distributed.Process hiding (Message)
+import GHC.Generics (Generic)
+import Data.Binary
+import Data.Typeable
+
+
+data Message content = Info Ping ProcessId  -- ^ message sent when nodes join cluster 
+                       | Message content    -- ^ other messages 
+                       deriving (Typeable, Generic, Show)
+
+data Ping = Ping | Pong deriving (Eq, Show, Generic)
+
+instance Binary content => Binary (Message content)
+instance Binary Ping
diff --git a/src/Control/Distributed/Raketka/Type/Server.hs b/src/Control/Distributed/Raketka/Type/Server.hs
new file mode 100644
--- /dev/null
+++ b/src/Control/Distributed/Raketka/Type/Server.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE RankNTypes #-}
+module Control.Distributed.Raketka.Type.Server where
+
+import Control.Concurrent.STM
+import Control.Distributed.Process.Serializable
+import Control.Distributed.Process hiding (Message)
+import Control.Distributed.Raketka.Type.Arg
+import Data.Tagged
+
+-- | constraint type
+type Content c = (Specific c, Serializable c, Show c)
+
+{- | see example implementation in "Control.Distributed.Raketka.Impl.Inst"
+
+    this file is part of the package, is not displayed in docs because it is part of a program, not the library.
+    
+    see also Main.hs there is important code there 
+-}
+class Specific c where
+    handleMessage::Tagged c Server -> c -> Process ()
+    startServer::Tagged c ServerId -> Process ()
+
+
+passTag::Tagged a b -> c -> Tagged a c
+passTag _ = Tagged
+
+
+data Server = Server
+      { proxychan::TChan (Process ())  -- ^ pipeline for sending messages 
+        , servers::TVar [ProcessId]    -- ^ to broadcast to entire cluster 
+        , spid::ProcessId              -- ^ this node's pid   
+        }
+               
diff --git a/src/Main.hs b/src/Main.hs
new file mode 100644
--- /dev/null
+++ b/src/Main.hs
@@ -0,0 +1,28 @@
+module Main where
+
+import Data.Tagged
+import Data.Conf.Json
+import System.Environment
+import Control.Distributed.Process.Closure
+import Control.Distributed.Process.Backend.SimpleLocalnet
+import Control.Distributed.Process.Node as Node hiding (newLocalNode)
+import Control.Distributed.Raketka.Master
+import Control.Distributed.Raketka.Type.Arg
+import Control.Distributed.Raketka.Impl.Inst as I
+
+
+remotable ['server]
+
+
+main::IO()
+main = do
+    [path0, idx0] <- getArgs 
+    Right c1 @ (Cluster conf1) <- readParse path0::IO (Either String Cluster)
+    let this1 = conf1 !! (read idx0::Int)
+        thisport1 = show $ port this1
+    backend1 <- initializeBackend (host this1) thisport1
+                    (__remoteTable initRemoteTable)
+    node1 <- newLocalNode backend1
+    Node.runProcess node1 
+            (master backend1 c1 
+                (Tagged $ read idx0::Tagged TByteString Int))
diff --git a/test-conf.json b/test-conf.json
new file mode 100644
--- /dev/null
+++ b/test-conf.json
@@ -0,0 +1,17 @@
+ [
+   { 
+    "host": "localhost",
+     "port": 54001,
+     "service": "hello"
+   },
+   {
+     "host": "localhost",
+     "port": 54002,
+     "service": "hello"
+   },
+   {
+     "host": "localhost",
+     "port": 54003,
+     "service": "hello"
+   }
+ ] 
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,4 @@
+module Main where
+
+main::IO()
+main = pure ()
