diff --git a/libs/buildSrc.jar b/libs/buildSrc.jar
deleted file mode 100644
index b0ca2cbc4fc7f12022592944d7446a429d855add..0000000000000000000000000000000000000000
Binary files a/libs/buildSrc.jar and /dev/null differ
diff --git a/ragconnect.base/src/main/jastadd/Configuration.jadd b/ragconnect.base/src/main/jastadd/Configuration.jadd
index 8d3b16c3f99014408c6aefa5655201d222c3d892..4116807c5b921b74bd645a992c849d841b43bcf2 100644
--- a/ragconnect.base/src/main/jastadd/Configuration.jadd
+++ b/ragconnect.base/src/main/jastadd/Configuration.jadd
@@ -4,4 +4,5 @@ aspect Configuration {
   public static TypeDecl ASTNode.rootNode;
   public static boolean ASTNode.usesMqtt;
   public static boolean ASTNode.usesRest;
+  public static boolean ASTNode.incrementalOptionActive;
 }
diff --git a/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java b/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java
index d2894e6c6475b3929f063fa7742a4bb11002c00d..b14ed91e9b6ba31824e7ffb72995c781e9689015 100644
--- a/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java
+++ b/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java
@@ -78,7 +78,6 @@ public class Compiler extends AbstractCompiler {
 
     printMessage("Writing output files");
     final List<String> handlers = new ArrayList<>();
-    handlers.add("RagConnectObserver.jadd");
     if (ASTNode.usesMqtt) {
       handlers.add("MqttHandler.jadd");
     }
@@ -224,6 +223,8 @@ public class Compiler extends AbstractCompiler {
     ragConnect.additionalRelations().forEach(ragConnectGrammarPart::addDeclaration);
     ASTNode.loggingEnabledForReads = optionLogReads.value();
     ASTNode.loggingEnabledForWrites = optionLogWrites.value();
+    // reuse "--incremental" option of JastAdd
+    ASTNode.incrementalOptionActive = getConfiguration().incremental() && getConfiguration().traceFlush();
     ASTNode.usesMqtt = optionProtocols.hasValue(OPTION_PROTOCOL_MQTT);
     ASTNode.usesRest = optionProtocols.hasValue(OPTION_PROTOCOL_REST);
     return ragConnect;
diff --git a/ragconnect.base/src/main/resources/MqttHandler.jadd b/ragconnect.base/src/main/resources/MqttHandler.jadd
index 6b0ea71960b7d465ea1ddd63b62c6b19caaffe70..987c0c451317098cdeabd7b2d7009a2e8ceda301 100644
--- a/ragconnect.base/src/main/resources/MqttHandler.jadd
+++ b/ragconnect.base/src/main/resources/MqttHandler.jadd
@@ -252,7 +252,7 @@ public class MqttHandler {
     }
     // register callback
     logger.debug("new connection for {}", topic);
-    if (callbacks.get(topic) == null) {
+    if (callbacks.get(topic) == null || callbacks.get(topic).isEmpty()) {
       callbacks.put(topic, new java.util.ArrayList<>());
 
       // subscribe at broker
diff --git a/ragconnect.base/src/main/resources/RagConnectObserver.jadd b/ragconnect.base/src/main/resources/RagConnectObserver.jadd
index 3658f7b6211802ec795b0b608770256c83b0f97c..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644
--- a/ragconnect.base/src/main/resources/RagConnectObserver.jadd
+++ b/ragconnect.base/src/main/resources/RagConnectObserver.jadd
@@ -1,54 +0,0 @@
-aspect RagConnectObserver {
-  class RagConnectObserver implements ASTState.Trace.Receiver {
-    ASTState.Trace.Receiver oldReceiver;
-    class RagConnectObserverEntry {
-      ASTNode node;
-      String attributeString;
-      Runnable attributeCall;
-      RagConnectObserverEntry(ASTNode node, String attributeString, Runnable attributeCall) {
-        this.node = node;
-        this.attributeString = attributeString;
-        this.attributeCall = attributeCall;
-      }
-    }
-    java.util.List<RagConnectObserverEntry> observedNodes = new java.util.ArrayList<>();
-    RagConnectObserver(ASTNode node) {
-      // set the receiver. potentially dangerous because overriding existing receiver!
-      oldReceiver = node.trace().getReceiver();
-      node.trace().setReceiver(this);
-    }
-    void add(ASTNode node, String attributeString, Runnable attributeCall) {
-      System.out.println("** observer add " + node + " on " + attributeString);
-      observedNodes.add(new RagConnectObserverEntry(node, attributeString, attributeCall));
-    }
-    void remove(ASTNode node, String attributeString, Runnable attributeCall) {
-      observedNodes.remove(new RagConnectObserverEntry(node, attributeString, attributeCall));
-    }
-    @Override
-    public void accept(ASTState.Trace.Event event, ASTNode node, String attribute, Object params, Object value) {
-      oldReceiver.accept(event, node, attribute, params, value);
-      // ignore all events but INC_FLUSH_ATTR
-      if (event != ASTState.Trace.Event.INC_FLUSH_ATTR) {
-        return;
-      }
-      System.out.println("** observer check INC_FLUSH_ATTR event");
-      // iterate through list, if matching pair. could maybe be more efficient.
-      for (RagConnectObserverEntry entry : observedNodes) {
-        if (entry.node.equals(node) && entry.attributeString.equals(attribute)) {
-          // hit. call the attribute/nta-token
-          System.out.println("** observer hit " + entry.node + " on " + entry.attributeString);
-          entry.attributeCall.run();
-        }
-      }
-    }
-  }
-
-  private static RagConnectObserver ASTNode._ragConnectObserverInstance;
-  RagConnectObserver ASTNode._ragConnectObserver() {
-    if (_ragConnectObserverInstance == null) {
-      // does not matter, which node is used to create the observer as ASTState/tracing is also static
-      _ragConnectObserverInstance = new RagConnectObserver(this);
-    }
-    return _ragConnectObserverInstance;
-  }
-}
diff --git a/ragconnect.base/src/main/resources/ragconnect.mustache b/ragconnect.base/src/main/resources/ragconnect.mustache
index 49d1721cbf0cf0131fe2248fe4e736906448bccf..d82f43093071a7bd4fb5ad05d341b5fc13f4e1cf 100644
--- a/ragconnect.base/src/main/resources/ragconnect.mustache
+++ b/ragconnect.base/src/main/resources/ragconnect.mustache
@@ -21,3 +21,62 @@ aspect ROS2RAG {
   {{> tokenComponent}}
   {{/TokenComponents}}
 }
+
+{{#incrementalOptionActive}}
+aspect RagConnectObserver {
+  class RagConnectObserver implements ASTState.Trace.Receiver {
+    ASTState.Trace.Receiver oldReceiver;
+    class RagConnectObserverEntry {
+      final ConnectToken connectToken;
+      final ASTNode node;
+      final String attributeString;
+      final Runnable attributeCall;
+      RagConnectObserverEntry(ConnectToken connectToken, ASTNode node, String attributeString, Runnable attributeCall) {
+        this.connectToken = connectToken;
+        this.node = node;
+        this.attributeString = attributeString;
+        this.attributeCall = attributeCall;
+      }
+    }
+    java.util.List<RagConnectObserverEntry> observedNodes = new java.util.ArrayList<>();
+    RagConnectObserver(ASTNode node) {
+      // set the receiver. potentially dangerous because overriding existing receiver!
+      oldReceiver = node.trace().getReceiver();
+      node.trace().setReceiver(this);
+    }
+    void add(ConnectToken connectToken, ASTNode node, String attributeString, Runnable attributeCall) {
+      System.out.println("** observer add " + node + " on " + attributeString);
+      observedNodes.add(new RagConnectObserverEntry(connectToken, node, attributeString, attributeCall));
+    }
+    void remove(ConnectToken connectToken) {
+      observedNodes.removeIf(entry -> entry.connectToken.equals(connectToken));
+    }
+    @Override
+    public void accept(ASTState.Trace.Event event, ASTNode node, String attribute, Object params, Object value) {
+      oldReceiver.accept(event, node, attribute, params, value);
+      // ignore all events but INC_FLUSH_ATTR
+      if (event != ASTState.Trace.Event.INC_FLUSH_ATTR) {
+        return;
+      }
+      System.out.println("** observer check INC_FLUSH_ATTR event");
+      // iterate through list, if matching pair. could maybe be more efficient.
+      for (RagConnectObserverEntry entry : observedNodes) {
+        if (entry.node.equals(node) && entry.attributeString.equals(attribute)) {
+          // hit. call the attribute/nta-token
+          System.out.println("** observer hit " + entry.node + " on " + entry.attributeString);
+          entry.attributeCall.run();
+        }
+      }
+    }
+  }
+
+  private static RagConnectObserver ASTNode._ragConnectObserverInstance;
+  RagConnectObserver ASTNode._ragConnectObserver() {
+    if (_ragConnectObserverInstance == null) {
+      // does not matter, which node is used to create the observer as ASTState/tracing is also static
+      _ragConnectObserverInstance = new RagConnectObserver(this);
+    }
+    return _ragConnectObserverInstance;
+  }
+}
+{{/incrementalOptionActive}}
diff --git a/ragconnect.base/src/main/resources/sendDefinition.mustache b/ragconnect.base/src/main/resources/sendDefinition.mustache
index 44db8f73db8c4c1484acbb4adc11a4f7f9251275..bee941ff75a105431db457c64ff24b769439a179 100644
--- a/ragconnect.base/src/main/resources/sendDefinition.mustache
+++ b/ragconnect.base/src/main/resources/sendDefinition.mustache
@@ -3,6 +3,12 @@ private byte[] {{parentTypeName}}.{{lastValue}} = null;
 
 public boolean {{parentTypeName}}.{{connectMethod}}(String {{connectParameterName}}, boolean writeCurrentValue) throws java.io.IOException {
   {{>handleUri}}
+  ConnectToken connectToken;
+  if (connectTokens.computeIfAbsent(this, astNode -> new java.util.HashMap<java.net.URI, ConnectToken>())
+                   .get(uri) != null) {
+    System.err.println("Already connected for " + uri + " on " + this + "!");
+    return true;
+  }
   switch (scheme) {
   {{#usesMqtt}}
     case "mqtt":
@@ -18,35 +24,45 @@ public boolean {{parentTypeName}}.{{connectMethod}}(String {{connectParameterNam
       if (writeCurrentValue) {
         {{writeMethod}}();
       }
+      connectToken = new ConnectToken(uri);
       break;
   {{/usesMqtt}}
   {{#usesRest}}
     case "rest":
-      ConnectToken connectToken = {{restHandlerAttribute}}().newGETConnection(uri, () -> {
+      connectToken = {{restHandlerAttribute}}().newGETConnection(uri, () -> {
         {{updateMethod}}();
         return new String({{lastValue}});
       });
       if (connectToken == null) {
         return false;
       }
-      connectTokens.computeIfAbsent(this, astNode -> new java.util.HashMap<java.net.URI, ConnectToken>())
-                   .put(uri, connectToken);
       break;
   {{/usesRest}}
     default:
       System.err.println("Unknown protocol '" + scheme + "'.");
       return false;
   }
-  _ragConnectObserver().add(this, "{{parentTypeName}}.get{{tokenName}}()", () -> {
+  connectTokens.computeIfAbsent(this, astNode -> new java.util.HashMap<java.net.URI, ConnectToken>())
+               .put(uri, connectToken);
+  {{#incrementalOptionActive}}
+  _ragConnectObserver().add(connectToken, this, "{{parentTypeName}}.get{{tokenName}}()", () -> {
     if (this.{{updateMethod}}()) {
       this.{{writeMethod}}();
     }
   });
+  {{/incrementalOptionActive}}
   return true;
 }
 
 public boolean {{parentTypeName}}.{{disconnectMethod}}(String {{connectParameterName}}) throws java.io.IOException {
   {{>handleUri}}
+  ConnectToken connectToken = connectTokens.get(this).remove(uri);
+  if (connectToken == null) {
+    System.err.println("Disconnect without connect for " + uri + " on " + this + "!");
+  }
+  {{#incrementalOptionActive}}
+  _ragConnectObserver().remove(connectToken);
+  {{/incrementalOptionActive}}
   switch (scheme) {
   {{#usesMqtt}}
     case "mqtt":
@@ -56,7 +72,7 @@ public boolean {{parentTypeName}}.{{disconnectMethod}}(String {{connectParameter
   {{/usesMqtt}}
   {{#usesRest}}
     case "rest":
-      {{restHandlerAttribute}}().disconnect(connectTokens.get(this).get(uri));
+      {{restHandlerAttribute}}().disconnect(connectToken);
       break;
   {{/usesRest}}
     default:
diff --git a/ragconnect.tests/build.gradle b/ragconnect.tests/build.gradle
index 9af1e5bb9de9878a66fc14564f2a0e504ab77abe..0788d6600618ef2adb3f1f524737278208bf6960 100644
--- a/ragconnect.tests/build.gradle
+++ b/ragconnect.tests/build.gradle
@@ -1,8 +1,14 @@
 buildscript {
-    repositories.mavenCentral()
+    repositories {
+        mavenCentral()
+        maven {
+            name "gitlab-maven"
+            url "https://git-st.inf.tu-dresden.de/api/v4/groups/jastadd/-/packages/maven"
+        }
+    }
     dependencies {
         classpath 'org.jastadd:jastaddgradle:1.13.3'
-        classpath fileTree(include: ['buildSrc.jar'], dir: '../libs')
+        classpath 'org.jastadd.relast:relast-test:0.2.4'
     }
 }
 
@@ -24,7 +30,6 @@ group = 'de.tudresden.inf.st'
 
 repositories {
     mavenCentral()
-    jcenter()
 }
 
 dependencies {
@@ -82,7 +87,9 @@ sourceSets.test.java.srcDir genSrc
 idea.module.generatedSourceDirs += genSrc
 
 clean {
-    delete 'src/test/02-after-ragconnect/*/', 'src/test/03-after-relast/*/', 'src/test/java-gen/*/'
+    delete fileTree(dir: 'src/test/02-after-ragconnect/', exclude: '.gitignore')
+    delete fileTree(dir: 'src/test/03-after-relast/', exclude: '.gitignore')
+    delete fileTree(dir: 'src/test/java-gen/', exclude: '.gitignore')
 }
 
 // --- Test: Example ---
@@ -332,6 +339,7 @@ task compileTutorialTest(type: RelastTest) {
     moreInputFiles 'src/test/01-input/tutorial/Test.jadd',
             'src/test/02-after-ragconnect/tutorial/MqttHandler.jadd',
             'src/test/02-after-ragconnect/tutorial/RagConnect.jadd'
+//    extraJastAddOptions "--tracing=cache,flush"
 }
 
 compileTestJava.dependsOn compileTutorialTest