diff --git a/include/n2n_typedefs.h b/include/n2n_typedefs.h
index 360b340de8eec8400c5dfc9beceb6d2f994ddde9..c8731342fef3fb3bb1ccce78e83f422d5322c3a1 100644
--- a/include/n2n_typedefs.h
+++ b/include/n2n_typedefs.h
@@ -345,6 +345,15 @@ struct n3n_runtime_data;

 /* *************************************************** */

+/* Minimal callbacks used by embedded mobile consumers. */
+typedef struct n3n_embed_callbacks {
+    void (*sn_registration_updated)(struct n3n_runtime_data *eee, time_t now, const n3n_sock_t *sn);
+    void (*main_loop_period)(struct n3n_runtime_data *eee, time_t now);
+    void (*sock_opened)(struct n3n_runtime_data *eee);
+} n3n_embed_callbacks_t;
+
+/* *************************************************** */
+
 // FIXME: this definition belongs in n3n/network_traffic_filter.h but
 // it is tangled up.
 // TODO: determine the value of using function pointers for these two
@@ -511,6 +520,8 @@ typedef struct slots slots_t;
 struct n3n_runtime_data {
     n2n_edge_conf_t conf;

+    n3n_embed_callbacks_t embed_callbacks;
+
     /* Status */
     bool                             *keep_running;                      /**< Pointer to edge loop stop/go flag */
     struct peer_info                 *curr_sn;                           /**< Currently active supernode. */
diff --git a/src/benchmark.c b/src/benchmark.c
index ce0feef88973ef0b0bfa30e57243fd2eb7b8d183..e5323e6ba7df6877cdf57ce41e30c939721d2448 100644
--- a/src/benchmark.c
+++ b/src/benchmark.c
@@ -17,7 +17,9 @@

 #ifndef _WIN32
 #include <sys/mman.h>           // for mmap, MAP_SHARED, MAP_ANONYMOUS
+#if !defined(__APPLE__)
 #include <sys/ptrace.h>         // for ptrace
+#endif
 #include <sys/types.h>          // for PTRACE_*
 #include <sys/wait.h>           // for wait, WIFSTOPPED
 #endif
@@ -765,7 +767,7 @@ void benchmark_run_ptrace (const int seconds, int filterc, char **filterv) {
     return;
 }

-#elif defined(DARWIN)
+#elif defined(DARWIN) || defined(__APPLE__)
 void benchmark_run_ptrace (const int seconds, int filterc, char **filterv) {
     fprintf(stderr,"Macos only partially implements ptrace support\n");
     return;
diff --git a/src/edge_utils.c b/src/edge_utils.c
index fdb0257d45e6f9fbdfc5549b1ca855237120640b..52478a5fcca61792b269f972324a5f54dcb94546 100644
--- a/src/edge_utils.c
+++ b/src/edge_utils.c
@@ -435,6 +435,10 @@ void supernode_connect (struct n3n_runtime_data *eee) {
         mainloop_register_fd(eee->sock, fd_info_proto_v3udp);
     }

+    if(eee->embed_callbacks.sock_opened) {
+        eee->embed_callbacks.sock_opened(eee);
+    }
+
     // REVISIT: TODO:
     // - add a management event for "new supernode socket" to make it simpler
     //   to track subscriptions externally
@@ -2880,6 +2884,10 @@ void process_pdu (struct n3n_runtime_data *eee,
                     send_grat_arps(eee);
                 }
                 eee->last_sup = now;
+
+                if(eee->embed_callbacks.sn_registration_updated) {
+                    eee->embed_callbacks.sn_registration_updated(eee, now, &sender);
+                }
             }

             // NOTE: the register_interval should be chosen by the edge node based on its NAT configuration.
@@ -3331,6 +3339,9 @@ int run_edge_loop (struct n3n_runtime_data *eee) {
             }
         }

+        if(eee->embed_callbacks.main_loop_period) {
+            eee->embed_callbacks.main_loop_period(eee, now);
+        }

     } /* while */

@@ -3367,14 +3378,16 @@ void edge_term_conf (n2n_edge_conf_t *conf) {

     // - have a helper to calculate/remember the socket pathname
 #ifndef _WIN32
-    char unixsock[1024];
-    snprintf(unixsock, sizeof(unixsock), "%s/mgmt", conf->sessiondir);
-    unlink(unixsock);
     if(conf->sessiondir) {
+        char unixsock[1024];
+        snprintf(unixsock, sizeof(unixsock), "%s/mgmt", conf->sessiondir);
+        unlink(unixsock);
         rmdir(conf->sessiondir);
     }
 #else
-    _rmdir(conf->sessiondir);
+    if(conf->sessiondir) {
+        _rmdir(conf->sessiondir);
+    }
 #endif
     // Ignore errors in the unlink/rmdir as they could simply be that the
     // paths were chown/chmod by the administrator
@@ -3453,7 +3466,9 @@ void edge_term (struct n3n_runtime_data * eee) {

     free(eee);

+#ifndef N3N_EMBEDDED_MANAGED_LIFECYCLE
     n3n_deinitfuncs();
+#endif

 #ifdef _WIN32
     destroyWin32();
@@ -3485,27 +3500,29 @@ static int edge_init_sockets (struct n3n_runtime_data *eee) {
 #endif
     }

-    n3n_config_setup_sessiondir(&eee->conf);
+    const int sessiondir_result = n3n_config_setup_sessiondir(&eee->conf);

 #ifndef _WIN32
-    char unixsock[1024];
-    snprintf(unixsock, sizeof(unixsock), "%s/mgmt", eee->conf.sessiondir);
-
-    int fd = slots_create_listen_unix(
-        unixsock,
-        eee->conf.mgmt_sock_perms,
-        eee->conf.userid,
-        eee->conf.groupid
-    );
-    // TODO:
-    // - do we actually want to tie the user/group to the running pid?
-
-    if(fd < 0) {
-        perror("slots_listen_unix");
-        edge_term(eee);
-        exit(1);
+    if((sessiondir_result == 0) && eee->conf.sessiondir) {
+        char unixsock[1024];
+        snprintf(unixsock, sizeof(unixsock), "%s/mgmt", eee->conf.sessiondir);
+
+        int fd = slots_create_listen_unix(
+            unixsock,
+            eee->conf.mgmt_sock_perms,
+            eee->conf.userid,
+            eee->conf.groupid
+        );
+        // The management socket is optional for an embedded edge. Sandboxed
+        // packet-tunnel processes may not have a writable runtime directory.
+        if(fd < 0) {
+            traceEvent(TRACE_WARNING, "unable to open management socket %s; continuing without it", unixsock);
+        } else {
+            mainloop_register_fd(fd, fd_info_proto_listen_http);
+        }
+    } else {
+        traceEvent(TRACE_WARNING, "unable to create management session directory; continuing without it");
     }
-    mainloop_register_fd(fd, fd_info_proto_listen_http);
 #endif

 #ifndef SKIP_MULTICAST_PEERS_DISCOVERY
