diff --git a/CHANGES b/CHANGES
index e7998d4e1c1db58d03ff92809235871ce96f5663..19946ab9d4a4c067b54241729f263ba9b505626e 100644
--- a/CHANGES
+++ b/CHANGES
@@ -98,6 +98,14 @@ Queue
    AgentConnect, AgentComplete, AgentDump, and AgentRingNoAnswer will always be
    sent.  The "Variable" fields will also no longer exist on the Agent* events.
 
+ * The queue log now differentiates between blind and attended transfers. A
+   blind transfer will result in a BLINDTRANSFER message with the destination
+   context and extension. An attended transfer will result in an
+   ATTENDEDTRANSFER message. This message will indicate the method by which
+   the attended transfer was completed: "BRIDGE" for a bridge merge, "APP"
+   for running an application on a bridge or channel, or "LINK" for linking
+   two bridges together with local channels.
+
  * Queues now support a hint for member paused state. The hint uses the form
    'Queue:{queue_name}_pause_{member_name}', where {queue_name} and {member_name}
    are the name of the queue and the name of the member to subscribe to,
diff --git a/apps/app_queue.c b/apps/app_queue.c
index 30b5ed67097ea4ed7dd4a30f2119d8cd99325bad..bd7958767b3e14688766175bb5695fd4317f1e7a 100644
--- a/apps/app_queue.c
+++ b/apps/app_queue.c
@@ -108,6 +108,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/stasis_channels.h"
 #include "asterisk/stasis_message_router.h"
 #include "asterisk/bridge_after.h"
+#include "asterisk/stasis_bridges.h"
+#include "asterisk/core_local.h"
+#include "asterisk/mixmonitor.h"
+#include "asterisk/core_unreal.h"
+#include "asterisk/bridge_basic.h"
 
 /* Define, to debug reference counts on queues, without debugging reference counts on queue members */
 /* #define REF_DEBUG_ONLY_QUEUES */
@@ -1584,10 +1589,6 @@ static void update_realtime_members(struct call_queue *q);
 static struct member *interface_exists(struct call_queue *q, const char *interface);
 static int set_member_paused(const char *queuename, const char *interface, const char *reason, int paused);
 
-#if 0	// BUGBUG
-static void queue_transfer_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan);
-#endif	// BUGBUG
-
 static struct member *find_member_by_queuename_and_interface(const char *queuename, const char *interface);
 /*! \brief sets the QUEUESTATUS channel variable */
 static void set_queue_result(struct ast_channel *chan, enum queue_result res)
@@ -1734,7 +1735,9 @@ static inline struct call_queue *_queue_ref(struct call_queue *q, const char *ta
 
 static inline struct call_queue *_queue_unref(struct call_queue *q, const char *tag, const char *file, int line, const char *filename)
 {
-	__ao2_ref_debug(q, -1, tag, file, line, filename);
+	if (q) {
+		__ao2_ref_debug(q, -1, tag, file, line, filename);
+	}
 	return NULL;
 }
 
@@ -1753,7 +1756,9 @@ static inline struct call_queue *queue_ref(struct call_queue *q)
 
 static inline struct call_queue *queue_unref(struct call_queue *q)
 {
-	ao2_ref(q, -1);
+	if (q) {
+		ao2_ref(q, -1);
+	}
 	return NULL;
 }
 #endif
@@ -1906,25 +1911,19 @@ static void queue_member_manager_event(void *data,
 		"%s", ast_str_buffer(event_string));
 }
 
-static void queue_publish_multi_channel_blob(struct ast_channel *caller, struct ast_channel *agent, struct stasis_message_type *type, struct ast_json *blob)
+static void queue_publish_multi_channel_snapshot_blob(struct stasis_topic *topic,
+		struct ast_channel_snapshot *caller_snapshot,
+		struct ast_channel_snapshot *agent_snapshot,
+		struct stasis_message_type *type, struct ast_json *blob)
 {
 	RAII_VAR(struct ast_multi_channel_blob *, payload, NULL, ao2_cleanup);
 	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
-	struct ast_channel_snapshot *caller_snapshot;
-	struct ast_channel_snapshot *agent_snapshot;
 
 	payload = ast_multi_channel_blob_create(blob);
 	if (!payload) {
 		return;
 	}
 
-	caller_snapshot = ast_channel_snapshot_create(caller);
-	agent_snapshot = ast_channel_snapshot_create(agent);
-
-	if (!caller_snapshot || !agent_snapshot) {
-		return;
-	}
-
 	ast_multi_channel_blob_add_channel(payload, "caller", caller_snapshot);
 	ast_multi_channel_blob_add_channel(payload, "agent", agent_snapshot);
 
@@ -1933,7 +1932,24 @@ static void queue_publish_multi_channel_blob(struct ast_channel *caller, struct
 		return;
 	}
 
-	stasis_publish(ast_channel_topic(caller), msg);
+	stasis_publish(topic, msg);
+}
+
+static void queue_publish_multi_channel_blob(struct ast_channel *caller, struct ast_channel *agent,
+		struct stasis_message_type *type, struct ast_json *blob)
+{
+	RAII_VAR(struct ast_channel_snapshot *, caller_snapshot, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_channel_snapshot *, agent_snapshot, NULL, ao2_cleanup);
+
+	caller_snapshot = ast_channel_snapshot_create(caller);
+	agent_snapshot = ast_channel_snapshot_create(agent);
+
+	if (!caller_snapshot || !agent_snapshot) {
+		return;
+	}
+
+	queue_publish_multi_channel_snapshot_blob(ast_channel_topic(caller), caller_snapshot,
+			agent_snapshot, type, blob);
 }
 
 static void queue_publish_member_blob(struct stasis_message_type *type, struct ast_json *blob)
@@ -3927,7 +3943,7 @@ static int ring_entry(struct queue_ent *qe, struct callattempt *tmp, int *busies
 
 		member_call_pending_clear(tmp->member);
 
-		/* BUGBUG: Raise a BUSY dial end message here */
+		publish_dial_end_event(qe->chan, tmp, NULL, "BUSY");
 		tmp->stillgoing = 0;
 		++*busies;
 		return 0;
@@ -4350,14 +4366,8 @@ static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callatte
 		if (pos == 1 /* not found */) {
 			if (numlines == (numbusies + numnochan)) {
 				ast_debug(1, "Everyone is busy at this time\n");
-				/* BUGBUG: We shouldn't have to set anything here, as each
-				 * individual dial attempt should have set that CDR to busy
-				 */
 			} else {
 				ast_debug(3, "No one is answering queue '%s' (%d numlines / %d busies / %d failed channels)\n", queue, numlines, numbusies, numnochan);
-				/* BUGBUG: We shouldn't have to set anything here, as each
-				 * individual dial attempt should have set that CDR to busy
-				 */
 			}
 			*to = 0;
 			return NULL;
@@ -4983,7 +4993,6 @@ static int wait_our_turn(struct queue_ent *qe, int ringing, enum queue_result *r
 	return res;
 }
 
-#if 0	// BUGBUG
 /*!
  * \brief update the queue status
  * \retval Always 0
@@ -5028,7 +5037,6 @@ static int update_queue(struct call_queue *q, struct member *member, int callcom
 	ao2_unlock(q);
 	return 0;
 }
-#endif	// BUGBUG
 
 /*! \brief Calculate the metric of each member in the outgoing callattempts
  *
@@ -5117,15 +5125,17 @@ enum agent_complete_reason {
 	TRANSFER
 };
 
-#if 0	// BUGBUG
 /*! \brief Send out AMI message with member call completion status information */
-static void send_agent_complete(const struct queue_ent *qe, const char *queuename,
-	const struct ast_channel *peer, const struct member *member, time_t callstart,
-	char *vars, size_t vars_len, enum agent_complete_reason rsn)
+static void send_agent_complete(const char *queuename, struct ast_channel_snapshot *caller,
+	struct ast_channel_snapshot *peer, const struct member *member, time_t holdstart,
+	time_t callstart, enum agent_complete_reason rsn)
 {
 	const char *reason = NULL;	/* silence dumb compilers */
 	RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
 
+	ast_assert(peer != NULL);
+	ast_assert(caller != NULL);
+
 	switch (rsn) {
 	case CALLER:
 		reason = "caller";
@@ -5142,121 +5152,656 @@ static void send_agent_complete(const struct queue_ent *qe, const char *queuenam
 			     "Queue", queuename,
 			     "Interface", member->interface,
 			     "MemberName", member->membername,
-			     "HoldTime", (long)(callstart - qe->start)
-			     "TalkTime", (long)(time(NULL) - callstart)
+			     "HoldTime", (long)(callstart - holdstart),
+			     "TalkTime", (long)(time(NULL) - callstart),
 			     "Reason", reason);
-	queue_publish_multi_channel_blob(qe->chan, peer, queue_agent_complete_type(), blob);
+
+	queue_publish_multi_channel_snapshot_blob(ast_queue_topic(queuename), caller, peer,
+			queue_agent_complete_type(), blob);
 }
-#endif	// BUGBUG
 
-#if 0	// BUGBUG
-struct queue_transfer_ds {
-	struct queue_ent *qe;
+static void queue_agent_cb(void *userdata, struct stasis_subscription *sub,
+		struct stasis_topic *topic, struct stasis_message *msg)
+{
+	struct ast_channel_blob *agent_blob;
+
+	agent_blob = stasis_message_data(msg);
+
+	if (ast_channel_agent_login_type() == stasis_message_type(msg)) {
+		ast_queue_log("NONE", agent_blob->snapshot->uniqueid,
+			ast_json_string_get(ast_json_object_get(agent_blob->blob, "agent")),
+			"AGENTLOGIN", "%s", agent_blob->snapshot->name);
+	} else if (ast_channel_agent_logoff_type() == stasis_message_type(msg)) {
+		ast_queue_log("NONE", agent_blob->snapshot->uniqueid,
+			ast_json_string_get(ast_json_object_get(agent_blob->blob, "agent")),
+			"AGENTLOGOFF", "%s|%ld", agent_blob->snapshot->name,
+			(long) ast_json_integer_get(ast_json_object_get(agent_blob->blob, "logintime")));
+	}
+}
+
+/*!
+ * \brief Structure representing relevant data during a local channel optimization
+ *
+ * The reason we care about local channel optimizations is that we want to be able
+ * to accurately report when the caller and queue member have stopped talking to
+ * each other. A local channel optimization can cause it to appear that the conversation
+ * has stopped immediately after it has begun. By tracking that the relevant channels
+ * to monitor have changed due to a local channel optimization, we can give accurate
+ * reports.
+ *
+ * Local channel optimizations for queues are restricted from their normal operation.
+ * Bridges created by queues can only be the destination of local channel optimizations,
+ * not the source. In addition, move-swap local channel optimizations are the only
+ * permitted types of local channel optimization.
+ *
+ * This data is populated when we are told that a local channel optimization begin
+ * is occurring. When we get told the optimization has ended successfully, we then
+ * apply the data here into the queue_stasis_data.
+ */
+struct local_optimization {
+	/*! The uniqueid of the channel that will be taking the place of the caller or member */
+	const char *source_chan_uniqueid;
+	/*! Indication of whether we think there is a local channel optimization in progress */
+	int in_progress;
+	/*! The identifier for this local channel optimization */
+	unsigned int id;
+};
+
+/*!
+ * \brief User data for stasis subscriptions used for queue calls.
+ *
+ * app_queue subscribes to channel and bridge events for all bridged calls.
+ * app_queue cares about the following events:
+ *
+ * \li bridge enter: To determine the unique ID of the bridge created for the call.
+ * \li blind transfer: To send an appropriate agent complete event.
+ * \li attended transfer: To send an appropriate agent complete event.
+ * \li local optimization: To update caller and member unique IDs for the call.
+ * \li hangup: To send an appropriate agent complete event.
+ *
+ * The stasis subscriptions last until we determine that the caller and the member
+ * are no longer bridged with each other.
+ */
+struct queue_stasis_data {
+	AST_DECLARE_STRING_FIELDS(
+		/*! The unique ID of the caller's channel. */
+		AST_STRING_FIELD(caller_uniqueid);
+		/*! The unique ID of the queue member's channel */
+		AST_STRING_FIELD(member_uniqueid);
+		/*! The unique ID of the bridge created by the queue */
+		AST_STRING_FIELD(bridge_uniqueid);
+	);
+	/*! The relevant queue */
+	struct call_queue *queue;
+	/*! The queue member that has answered the call */
 	struct member *member;
+	/*! The time at which the caller entered the queue. Start of the caller's hold time */
+	time_t holdstart;
+	/*! The time at which the member answered the call. */
 	time_t starttime;
+	/*! The original position of the caller when he entered the queue */
+	int caller_pos;
+	/*! Indication if the call was answered within the configured service level of the queue */
 	int callcompletedinsl;
+	/*! Indicates if the stasis subscriptions are shutting down */
+	int dying;
+	/*! The stasis message router for bridge events */
+	struct stasis_message_router *bridge_router;
+	/*! The stasis message router for channel events */
+	struct stasis_message_router *channel_router;
+	/*! Local channel optimization details for the caller */
+	struct local_optimization caller_optimize;
+	/*! Local channel optimization details for the member */
+	struct local_optimization member_optimize;
 };
-#endif	// BUGBUG
 
-#if 0	// BUGBUG
-static void queue_transfer_destroy(void *data)
+/*!
+ * \internal
+ * \brief Free memory for a queue_stasis_data
+ */
+static void queue_stasis_data_destructor(void *obj)
 {
-	struct queue_transfer_ds *qtds = data;
-	ast_free(qtds);
+	struct queue_stasis_data *queue_data = obj;
+
+	/* This can only happen if refcounts for this object have got severely messed up */
+	ast_assert(queue_data->bridge_router == NULL);
+	ast_assert(queue_data->channel_router == NULL);
+
+	ao2_cleanup(queue_data->member);
+	queue_unref(queue_data->queue);
+	ast_string_field_free_memory(queue_data);
 }
-#endif	// BUGBUG
 
-#if 0	// BUGBUG
-/*! \brief a datastore used to help correctly log attended transfers of queue callers
+/*!
+ * \internal
+ * \brief End all stasis subscriptions on a queue_stasis_data
  */
-static const struct ast_datastore_info queue_transfer_info = {
-	.type = "queue_transfer",
-	.chan_fixup = queue_transfer_fixup,
-	.destroy = queue_transfer_destroy,
-};
-#endif	// BUGBUG
+static void remove_stasis_subscriptions(struct queue_stasis_data *queue_data)
+{
+	SCOPED_AO2LOCK(lock, queue_data);
+
+	queue_data->dying = 1;
+	stasis_message_router_unsubscribe(queue_data->bridge_router);
+	queue_data->bridge_router = NULL;
+	stasis_message_router_unsubscribe(queue_data->channel_router);
+	queue_data->channel_router = NULL;
+}
+
+/*!
+ * \internal
+ * \brief Allocate a queue_stasis_data and initialize its data.
+ */
+static struct queue_stasis_data *queue_stasis_data_alloc(struct queue_ent *qe,
+		struct ast_channel *peer, struct member *mem, time_t holdstart,
+		time_t starttime, int callcompletedinsl)
+{
+	struct queue_stasis_data *queue_data;
+
+	queue_data = ao2_alloc(sizeof(*queue_data), queue_stasis_data_destructor);
+	if (!queue_data) {
+		return NULL;
+	}
+
+	if (ast_string_field_init(queue_data, 64)) {
+		ao2_cleanup(queue_data);
+		return NULL;
+	}
 
-#if 0	// BUGBUG
-/*! \brief Log an attended transfer when a queue caller channel is masqueraded
+	ast_string_field_set(queue_data, caller_uniqueid, ast_channel_uniqueid(qe->chan));
+	ast_string_field_set(queue_data, member_uniqueid, ast_channel_uniqueid(peer));
+	queue_data->queue = queue_ref(qe->parent);
+	queue_data->starttime = starttime;
+	queue_data->holdstart = holdstart;
+	queue_data->callcompletedinsl = callcompletedinsl;
+	queue_data->caller_pos = qe->opos;
+	ao2_ref(mem, +1);
+	queue_data->member = mem;
+	return queue_data;
+}
+
+/*!
+ * \internal
+ * \brief Log an attended transfer in the queue log.
  *
- * When a caller is masqueraded, we want to log a transfer. Fixup time is the closest we can come to when
- * the actual transfer occurs. This happens during the masquerade after datastores are moved from old_chan
- * to new_chan. This is why new_chan is referenced for exten, context, and datastore information.
+ * Attended transfer queue log messages vary based on the method by which the
+ * attended transfer was completed.
  *
- * At the end of this, we want to remove the datastore so that this fixup function is not called on any
- * future masquerades of the caller during the current call.
+ * \param queue_data Data pertaining to the particular call in the queue.
+ * \param caller The channel snapshot for the caller channel in the queue.
+ * \param atxfer_msg The stasis attended transfer message data.
  */
-static void queue_transfer_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan)
+static void log_attended_transfer(struct queue_stasis_data *queue_data, struct ast_channel_snapshot *caller,
+		struct ast_attended_transfer_message *atxfer_msg)
 {
-	struct queue_transfer_ds *qtds = data;
-	struct queue_ent *qe = qtds->qe;
-	struct member *member = qtds->member;
-	time_t callstart = qtds->starttime;
-	int callcompletedinsl = qtds->callcompletedinsl;
-	struct ast_datastore *datastore;
+	RAII_VAR(struct ast_str *, transfer_str, ast_str_create(32), ast_free);
+
+	if (!transfer_str) {
+		ast_log(LOG_WARNING, "Unable to log attended transfer to queue log\n");
+		return;
+	}
+
+	switch (atxfer_msg->dest_type) {
+	case AST_ATTENDED_TRANSFER_DEST_BRIDGE_MERGE:
+		ast_str_set(&transfer_str, 0, "BRIDGE|%s", atxfer_msg->dest.bridge);
+		break;
+	case AST_ATTENDED_TRANSFER_DEST_APP:
+		ast_str_set(&transfer_str, 0, "APP|%s", atxfer_msg->dest.app);
+		break;
+	case AST_ATTENDED_TRANSFER_DEST_LINK:
+		ast_str_set(&transfer_str, 0, "LINK|%s|%s", atxfer_msg->dest.links[0]->name,
+				atxfer_msg->dest.links[1]->name);
+		break;
+	case AST_ATTENDED_TRANSFER_DEST_THREEWAY:
+	case AST_ATTENDED_TRANSFER_DEST_FAIL:
+		/* Threeways are headed off and should not be logged here */
+		ast_assert(0);
+		return;
+	}
+
+	ast_queue_log(queue_data->queue->name, caller->uniqueid, queue_data->member->membername, "ATTENDEDTRANSFER", "%s|%ld|%ld|%d",
+			ast_str_buffer(transfer_str),
+			(long) queue_data->starttime - queue_data->holdstart,
+			(long) time(NULL) - queue_data->starttime, queue_data->caller_pos);
+}
 
-	ast_queue_log(qe->parent->name, ast_channel_uniqueid(qe->chan), member->membername, "TRANSFER", "%s|%s|%ld|%ld|%d",
-				ast_channel_exten(new_chan), ast_channel_context(new_chan), (long) (callstart - qe->start),
-				(long) (time(NULL) - callstart), qe->opos);
+/*!
+ * \internal
+ * \brief Handle a stasis bridge enter event.
+ *
+ * We track this particular event in order to learn what bridge
+ * was created for the queue call.
+ *
+ * \param userdata Data pertaining to the particular call in the queue.
+ * \param sub The stasis subscription on which the message occurred.
+ * \param topic The topic for this event.
+ * \param msg The stasis message for the bridge enter event
+ */
+static void handle_bridge_enter(void *userdata, struct stasis_subscription *sub,
+		struct stasis_topic *topic, struct stasis_message *msg)
+{
+	struct queue_stasis_data *queue_data = userdata;
+	struct ast_bridge_blob *enter_blob = stasis_message_data(msg);
+
+	if (queue_data->dying) {
+		return;
+	}
+
+	if (!ast_strlen_zero(queue_data->bridge_uniqueid)) {
+		return;
+	}
 
-	update_queue(qe->parent, member, callcompletedinsl, (time(NULL) - callstart));
+	if (!strcmp(enter_blob->channel->uniqueid, queue_data->caller_uniqueid)) {
+		ast_string_field_set(queue_data, bridge_uniqueid,
+				enter_blob->bridge->uniqueid);
+		ast_debug(3, "Detected entry of caller channel %s into bridge %s\n",
+				enter_blob->channel->name, queue_data->bridge_uniqueid);
+	}
+}
 
-	/* No need to lock the channels because they are already locked in ast_do_masquerade */
-	if ((datastore = ast_channel_datastore_find(old_chan, &queue_transfer_info, NULL))) {
-		ast_channel_datastore_remove(old_chan, datastore);
+/*!
+ * \brief Handle a blind transfer event
+ *
+ * This event is important in order to be able to log the end of the
+ * call to the queue log and to stasis.
+ *
+ * \param userdata Data pertaining to the particular call in the queue.
+ * \param sub The stasis subscription on which the message occurred.
+ * \param topic The topic for this event.
+ * \param msg The stasis message for the blind transfer event
+ */
+static void handle_blind_transfer(void *userdata, struct stasis_subscription *sub,
+		struct stasis_topic *topic, struct stasis_message *msg)
+{
+	struct queue_stasis_data *queue_data = userdata;
+	struct ast_bridge_blob *blind_blob = stasis_message_data(msg);
+	struct ast_json *result_blob;
+	struct ast_json *exten_blob;
+	struct ast_json *context_blob;
+	const char *exten;
+	const char *context;
+	RAII_VAR(struct ast_channel_snapshot *, caller_snapshot, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_channel_snapshot *, member_snapshot, NULL, ao2_cleanup);
+
+	if (queue_data->dying) {
+		return;
+	}
+
+	result_blob = ast_json_object_get(blind_blob->blob, "result");
+	if (!result_blob) {
+		return;
+	}
+
+	if (ast_json_integer_get(result_blob) == AST_BRIDGE_TRANSFER_FAIL) {
+		return;
+	}
+
+	ao2_lock(queue_data);
+
+	if (ast_strlen_zero(queue_data->bridge_uniqueid) ||
+			strcmp(queue_data->bridge_uniqueid, blind_blob->bridge->uniqueid)) {
+		ao2_unlock(queue_data);
+		return;
+	}
+
+	caller_snapshot = ast_channel_snapshot_get_latest(queue_data->caller_uniqueid);
+	member_snapshot = ast_channel_snapshot_get_latest(queue_data->member_uniqueid);
+
+	ao2_unlock(queue_data);
+
+	exten_blob = ast_json_object_get(blind_blob->blob, "exten");
+	exten = exten_blob ? ast_json_string_get(exten_blob) : "<unknown>";
+	context_blob = ast_json_object_get(blind_blob->blob, "context");
+	context = context_blob ? ast_json_string_get(context_blob) : "<unknown>";
+
+	ast_debug(3, "Detected blind transfer in queue %s\n", queue_data->queue->name);
+	ast_queue_log(queue_data->queue->name, caller_snapshot->uniqueid, queue_data->member->membername,
+			"BLINDTRANSFER", "%s|%s|%ld|%ld|%d",
+			exten, context,
+			(long) queue_data->starttime - queue_data->holdstart,
+			(long) time(NULL) - queue_data->starttime, queue_data->caller_pos);
+
+	send_agent_complete(queue_data->queue->name, caller_snapshot, member_snapshot, queue_data->member,
+			queue_data->holdstart, queue_data->starttime, TRANSFER);
+	update_queue(queue_data->queue, queue_data->member, queue_data->callcompletedinsl,
+			time(NULL) - queue_data->starttime);
+	remove_stasis_subscriptions(queue_data);
+}
+
+/*!
+ * \brief Handle an attended transfer event
+ *
+ * This event is important in order to be able to log the end of the
+ * call to the queue log and to stasis.
+ *
+ * \param userdata Data pertaining to the particular call in the queue.
+ * \param sub The stasis subscription on which the message occurred.
+ * \param topic The topic for this event.
+ * \param msg The stasis message for the attended transfer event.
+ */
+static void handle_attended_transfer(void *userdata, struct stasis_subscription *sub,
+		struct stasis_topic *topic, struct stasis_message *msg)
+{
+	struct queue_stasis_data *queue_data = userdata;
+	struct ast_attended_transfer_message *atxfer_msg = stasis_message_data(msg);
+	RAII_VAR(struct ast_channel_snapshot *, caller_snapshot, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_channel_snapshot *, member_snapshot, NULL, ao2_cleanup);
+
+	if (queue_data->dying) {
+		return;
+	}
+
+	if (atxfer_msg->result == AST_BRIDGE_TRANSFER_FAIL ||
+			atxfer_msg->dest_type == AST_ATTENDED_TRANSFER_DEST_THREEWAY) {
+		return;
+	}
+
+	ao2_lock(queue_data);
+
+	if (ast_strlen_zero(queue_data->bridge_uniqueid)) {
+		ao2_unlock(queue_data);
+		return;
+	}
+
+	if ((!atxfer_msg->to_transferee.bridge_snapshot || strcmp(queue_data->bridge_uniqueid,
+					atxfer_msg->to_transferee.bridge_snapshot->uniqueid)) &&
+			 (!atxfer_msg->to_transfer_target.bridge_snapshot || strcmp(queue_data->bridge_uniqueid,
+				 atxfer_msg->to_transfer_target.bridge_snapshot->uniqueid))) {
+		ao2_unlock(queue_data);
+		return;
+	}
+
+	caller_snapshot = ast_channel_snapshot_get_latest(queue_data->caller_uniqueid);
+	member_snapshot = ast_channel_snapshot_get_latest(queue_data->member_uniqueid);
+
+	ao2_unlock(queue_data);
+
+	ast_debug(3, "Detected attended transfer in queue %s\n", queue_data->queue->name);
+
+	log_attended_transfer(queue_data, caller_snapshot, atxfer_msg);
+
+	send_agent_complete(queue_data->queue->name, caller_snapshot, member_snapshot, queue_data->member,
+			queue_data->holdstart, queue_data->starttime, TRANSFER);
+	update_queue(queue_data->queue, queue_data->member, queue_data->callcompletedinsl,
+			time(NULL) - queue_data->starttime);
+	remove_stasis_subscriptions(queue_data);
+}
+
+/*!
+ * \internal
+ * \brief Callback for all stasis bridge events
+ *
+ * Based on the event and what bridge it is on, the task is farmed out to relevant
+ * subroutines for further processing.
+ */
+static void queue_bridge_cb(void *userdata, struct stasis_subscription *sub,
+		struct stasis_topic *topic, struct stasis_message *msg)
+{
+	if (stasis_subscription_final_message(sub, msg)) {
+		ao2_cleanup(userdata);
+	}
+}
+
+/*!
+ * \internal
+ * \brief Handler for the beginning of a local channel optimization
+ *
+ * This method gathers data relevant to the local channel optimization and stores
+ * it to be used once the local optimization completes.
+ *
+ * \param userdata Data pertaining to the particular call in the queue.
+ * \param sub The stasis subscription on which the message occurred.
+ * \param topic The topic for this event.
+ * \param msg The stasis message for the local optimization begin event
+ */
+static void handle_local_optimization_begin(void *userdata, struct stasis_subscription *sub,
+		struct stasis_topic *topic, struct stasis_message *msg)
+{
+	struct queue_stasis_data *queue_data = userdata;
+	struct ast_multi_channel_blob *optimization_blob = stasis_message_data(msg);
+	struct ast_channel_snapshot *local_one = ast_multi_channel_blob_get_channel(optimization_blob, "1");
+	struct ast_channel_snapshot *local_two = ast_multi_channel_blob_get_channel(optimization_blob, "2");
+	struct ast_channel_snapshot *source = ast_multi_channel_blob_get_channel(optimization_blob, "source");
+	struct local_optimization *optimization;
+	unsigned int id;
+	SCOPED_AO2LOCK(lock, queue_data);
+
+	if (queue_data->dying) {
+		return;
+	}
+
+	if (!strcmp(local_one->uniqueid, queue_data->member_uniqueid)) {
+		optimization = &queue_data->member_optimize;
+	} else if (!strcmp(local_two->uniqueid, queue_data->caller_uniqueid)) {
+		optimization = &queue_data->caller_optimize;
 	} else {
-		ast_log(LOG_WARNING, "Can't find the queue_transfer datastore.\n");
+		return;
+	}
+
+	/* We only allow move-swap optimizations, so there had BETTER be a source */
+	ast_assert(source != NULL);
+
+	optimization->source_chan_uniqueid = ast_strdup(source->uniqueid);
+	if (!optimization->source_chan_uniqueid) {
+		ast_log(LOG_ERROR, "Unable to track local channel optimization for channel %s. Expect further errors\n", local_one->name);
+		return;
 	}
+	id = ast_json_integer_get(ast_json_object_get(ast_multi_channel_blob_get_json(optimization_blob), "id"));
+
+	optimization->id = id;
+	optimization->in_progress = 1;
 }
-#endif	// BUGBUG
 
-#if 0	// BUGBUG
-/*! \brief mechanism to tell if a queue caller was atxferred by a queue member.
+/*!
+ * \internal
+ * \brief Handler for the end of a local channel optimization
  *
- * When a caller is atxferred, then the queue_transfer_info datastore
- * is removed from the channel. If it's still there after the bridge is
- * broken, then the caller was not atxferred.
+ * This method takes the data gathered during the local channel optimization begin
+ * event and applies it to the queue stasis data appropriately. This generally involves
+ * updating the caller or member unique ID with the channel that is taking the place of
+ * the previous caller or member.
  *
- * \note Only call this with chan locked
+ * \param userdata Data pertaining to the particular call in the queue.
+ * \param sub The stasis subscription on which the message occurred.
+ * \param topic The topic for this event.
+ * \param msg The stasis message for the local optimization end event
  */
-static int attended_transfer_occurred(struct ast_channel *chan)
+static void handle_local_optimization_end(void *userdata, struct stasis_subscription *sub,
+		struct stasis_topic *topic, struct stasis_message *msg)
+{
+	struct queue_stasis_data *queue_data = userdata;
+	struct ast_multi_channel_blob *optimization_blob = stasis_message_data(msg);
+	struct ast_channel_snapshot *local_one = ast_multi_channel_blob_get_channel(optimization_blob, "1");
+	struct ast_channel_snapshot *local_two = ast_multi_channel_blob_get_channel(optimization_blob, "2");
+	struct local_optimization *optimization;
+	int is_caller;
+	unsigned int id;
+	SCOPED_AO2LOCK(lock, queue_data);
+
+	if (queue_data->dying) {
+		return;
+	}
+
+	if (!strcmp(local_one->uniqueid, queue_data->member_uniqueid)) {
+		optimization = &queue_data->member_optimize;
+		is_caller = 0;
+	} else if (!strcmp(local_two->uniqueid, queue_data->caller_uniqueid)) {
+		optimization = &queue_data->caller_optimize;
+		is_caller = 1;
+	} else {
+		return;
+	}
+
+	id = ast_json_integer_get(ast_json_object_get(ast_multi_channel_blob_get_json(optimization_blob), "id"));
+
+	if (!optimization->in_progress) {
+		ast_log(LOG_WARNING, "Told of a local optimization end when we had no previous begin\n");
+		return;
+	}
+
+	if (id != optimization->id) {
+		ast_log(LOG_WARNING, "Local optimization end event ID does not match begin (%u != %u)\n",
+				id, optimization->id);
+		return;
+	}
+
+	if (is_caller) {
+		ast_debug(3, "Local optimization: Changing queue caller uniqueid from %s to %s\n",
+				queue_data->caller_uniqueid, optimization->source_chan_uniqueid);
+		ast_string_field_set(queue_data, caller_uniqueid, optimization->source_chan_uniqueid);
+	} else {
+		ast_debug(3, "Local optimization: Changing queue member uniqueid from %s to %s\n",
+				queue_data->member_uniqueid, optimization->source_chan_uniqueid);
+		ast_string_field_set(queue_data, member_uniqueid, optimization->source_chan_uniqueid);
+	}
+
+	optimization->in_progress = 0;
+}
+
+/*!
+ * \internal
+ * \brief Handler for hangup stasis event
+ *
+ * This is how we determine that the caller or member has hung up and the call
+ * has ended. An appropriate queue log and stasis message are raised in this
+ * callback.
+ *
+ * \param userdata Data pertaining to the particular call in the queue.
+ * \param sub The stasis subscription on which the message occurred.
+ * \param topic The topic for this event.
+ * \param msg The stasis message for the hangup event.
+ */
+static void handle_hangup(void *userdata, struct stasis_subscription *sub,
+		struct stasis_topic *topic, struct stasis_message *msg)
 {
-	return ast_channel_datastore_find(chan, &queue_transfer_info, NULL) ? 0 : 1;
+	struct queue_stasis_data *queue_data = userdata;
+	struct ast_channel_blob *channel_blob = stasis_message_data(msg);
+	RAII_VAR(struct ast_channel_snapshot *, caller_snapshot, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_channel_snapshot *, member_snapshot, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_channel *, chan, NULL, ao2_cleanup);
+	enum agent_complete_reason reason;
+
+	if (queue_data->dying) {
+		return;
+	}
+
+	ao2_lock(queue_data);
+
+	if (!strcmp(channel_blob->snapshot->uniqueid, queue_data->caller_uniqueid)) {
+		reason = CALLER;
+	} else if (!strcmp(channel_blob->snapshot->uniqueid, queue_data->member_uniqueid)) {
+		reason = AGENT;
+	} else {
+		ao2_unlock(queue_data);
+		return;
+	}
+
+	chan = ast_channel_get_by_name(channel_blob->snapshot->name);
+	if (chan && ast_channel_has_role(chan, AST_TRANSFERER_ROLE_NAME)) {
+		/* Channel that is hanging up is doing it as part of a transfer.
+		 * We'll get a transfer event later
+		 */
+		ao2_unlock(queue_data);
+		return;
+	}
+
+	caller_snapshot = ast_channel_snapshot_get_latest(queue_data->caller_uniqueid);
+	member_snapshot = ast_channel_snapshot_get_latest(queue_data->member_uniqueid);
+
+	ao2_unlock(queue_data);
+
+	ast_debug(3, "Detected hangup of queue %s channel %s\n", reason == CALLER ? "caller" : "member",
+			channel_blob->snapshot->name);
+
+	ast_queue_log(queue_data->queue->name, caller_snapshot->uniqueid, queue_data->member->membername,
+			reason == CALLER ? "COMPLETECALLER" : "COMPLETEAGENT", "%ld|%ld|%d",
+		(long) (queue_data->starttime - queue_data->holdstart),
+		(long) (time(NULL) - queue_data->starttime), queue_data->caller_pos);
+
+	send_agent_complete(queue_data->queue->name, caller_snapshot, member_snapshot, queue_data->member,
+			queue_data->holdstart, queue_data->starttime, reason);
+	update_queue(queue_data->queue, queue_data->member, queue_data->callcompletedinsl,
+			time(NULL) - queue_data->starttime);
+	remove_stasis_subscriptions(queue_data);
 }
-#endif	// BUGBUG
 
-#if 0	// BUGBUG
-/*! \brief create a datastore for storing relevant info to log attended transfers in the queue_log
+/*!
+ * \internal
+ * \brief Callback for all stasis channel events
+ *
+ * Based on the event and the channels involved, the work is farmed out into
+ * subroutines for further processing.
  */
-static struct ast_datastore *setup_transfer_datastore(struct queue_ent *qe, struct member *member, time_t starttime, int callcompletedinsl)
+static void queue_channel_cb(void *userdata, struct stasis_subscription *sub,
+		struct stasis_topic *topic, struct stasis_message *msg)
 {
-	struct ast_datastore *ds;
-	struct queue_transfer_ds *qtds = ast_calloc(1, sizeof(*qtds));
+	if (stasis_subscription_final_message(sub, msg)) {
+		ao2_cleanup(userdata);
+	}
+}
 
-	if (!qtds) {
-		ast_log(LOG_WARNING, "Memory allocation error!\n");
-		return NULL;
+/*!
+ * \internal
+ * \brief Create stasis subscriptions for a particular call in the queue.
+ *
+ * These subscriptions are created once the call has been answered. The subscriptions
+ * are put in place so that call progress may be tracked. Once the call can be determined
+ * to have ended, then messages are logged to the queue log and stasis events are emitted.
+ *
+ * \param qe The queue entry representing the caller
+ * \param peer The channel that has answered the call
+ * \param mem The queue member that answered the call
+ * \param holdstart The time at which the caller entered the queue
+ * \param starttime The time at which the call was answered
+ * \param callcompletedinsl Indicates if the call was answered within the configured service level of the queue.
+ * \retval 0 Success
+ * \retval non-zero Failure
+ */
+static int setup_stasis_subs(struct queue_ent *qe, struct ast_channel *peer, struct member *mem,
+		time_t holdstart, time_t starttime, int callcompletedinsl)
+{
+	struct queue_stasis_data *queue_data = queue_stasis_data_alloc(qe, peer, mem, holdstart, starttime, callcompletedinsl);
+
+	if (!queue_data) {
+		return -1;
 	}
 
-	ast_channel_lock(qe->chan);
-	if (!(ds = ast_datastore_alloc(&queue_transfer_info, NULL))) {
-		ast_channel_unlock(qe->chan);
-		ast_free(qtds);
-		ast_log(LOG_WARNING, "Unable to create transfer datastore. queue_log will not show attended transfer\n");
-		return NULL;
+	queue_data->bridge_router = stasis_message_router_create(ast_bridge_topic_all());
+	if (!queue_data->bridge_router) {
+		ao2_ref(queue_data, -1);
+		return -1;
 	}
 
-	qtds->qe = qe;
-	/* This member is refcounted in try_calling, so no need to add it here, too */
-	qtds->member = member;
-	qtds->starttime = starttime;
-	qtds->callcompletedinsl = callcompletedinsl;
-	ds->data = qtds;
-	ast_channel_datastore_add(qe->chan, ds);
-	ast_channel_unlock(qe->chan);
-	return ds;
+	stasis_message_router_add(queue_data->bridge_router, ast_channel_entered_bridge_type(),
+			handle_bridge_enter, queue_data);
+	stasis_message_router_add(queue_data->bridge_router, ast_blind_transfer_type(),
+			handle_blind_transfer, queue_data);
+	stasis_message_router_add(queue_data->bridge_router, ast_attended_transfer_type(),
+			handle_attended_transfer, queue_data);
+	stasis_message_router_set_default(queue_data->bridge_router,
+			queue_bridge_cb, queue_data);
+
+	queue_data->channel_router = stasis_message_router_create(ast_channel_topic_all());
+	if (!queue_data->channel_router) {
+		/* Unsubscribing from the bridge router will remove the only ref of queue_data,
+		 * thus beginning the destruction process
+		 */
+		stasis_message_router_unsubscribe(queue_data->bridge_router);
+		queue_data->bridge_router = NULL;
+		return -1;
+	}
+
+	ao2_ref(queue_data, +1);
+	stasis_message_router_add(queue_data->channel_router, ast_local_optimization_begin_type(),
+			handle_local_optimization_begin, queue_data);
+	stasis_message_router_add(queue_data->channel_router, ast_local_optimization_end_type(),
+			handle_local_optimization_end, queue_data);
+	stasis_message_router_add(queue_data->channel_router, ast_channel_hangup_request_type(),
+			handle_hangup, queue_data);
+	stasis_message_router_set_default(queue_data->channel_router,
+			queue_channel_cb, queue_data);
+
+	return 0;
 }
-#endif	// BUGBUG
 
 struct queue_end_bridge {
 	struct call_queue *q;
@@ -5312,6 +5857,82 @@ static void setup_peer_after_bridge_goto(struct ast_channel *chan, struct ast_ch
 	}
 }
 
+static void escape_and_substitute(struct ast_channel *chan, const char *input,
+		char *output, size_t size)
+{
+	const char *m = input;
+	char escaped[size];
+	char *p;
+
+	for (p = escaped; p < escaped + size - 1; p++, m++) {
+		switch (*m) {
+		case '^':
+			if (*(m + 1) == '{') {
+				*p = '$';
+			}
+			break;
+		case ',':
+			*p++ = '\\';
+			/* Fall through */
+		default:
+			*p = *m;
+		}
+		if (*m == '\0')
+			break;
+	}
+
+	if (p == escaped + size) {
+		escaped[size - 1] = '\0';
+	}
+
+	pbx_substitute_variables_helper(chan, escaped, output, size - 1);
+}
+
+static void setup_mixmonitor(struct queue_ent *qe, const char *filename)
+{
+	char escaped_filename[256];
+	char file_with_ext[256];
+	char mixmonargs[1512];
+	char escaped_monitor_exec[1024];
+	const char *monitor_options;
+	const char *monitor_exec;
+
+	if (filename) {
+		escape_and_substitute(qe->chan, filename, escaped_filename, sizeof(escaped_filename));
+	} else {
+		ast_copy_string(escaped_filename, ast_channel_uniqueid(qe->chan), sizeof(escaped_filename));
+	}
+
+	ast_channel_lock(qe->chan);
+	if ((monitor_exec = pbx_builtin_getvar_helper(qe->chan, "MONITOR_EXEC"))) {
+		monitor_exec = ast_strdupa(monitor_exec);
+	}
+	if ((monitor_options = pbx_builtin_getvar_helper(qe->chan, "MONITOR_OPTIONS"))) {
+		monitor_options = ast_strdupa(monitor_options);
+	} else {
+		monitor_options = "";
+	}
+	ast_channel_unlock(qe->chan);
+
+	if (monitor_exec) {
+		escape_and_substitute(qe->chan, monitor_exec, escaped_monitor_exec, sizeof(escaped_monitor_exec));
+	}
+
+	snprintf(file_with_ext, sizeof(file_with_ext), "%s.%s", escaped_filename, qe->parent->monfmt);
+
+	if (!ast_strlen_zero(escaped_monitor_exec)) {
+		snprintf(mixmonargs, sizeof(mixmonargs), "b%s,%s", monitor_options, escaped_monitor_exec);
+	} else {
+		snprintf(mixmonargs, sizeof(mixmonargs), "b%s", monitor_options);
+	}
+
+	ast_debug(1, "Arguments being passed to MixMonitor: %s,%s\n", file_with_ext, mixmonargs);
+
+	if (ast_start_mixmonitor(qe->chan, file_with_ext, mixmonargs)) {
+		ast_log(LOG_WARNING, "Unable to start mixmonitor. Is the MixMonitor app loaded?\n");
+	}
+}
+
 /*!
  * \internal
  * \brief A large function which calls members, updates statistics, and bridges the caller and a member
@@ -5361,9 +5982,7 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a
 	int x=0;
 	char *announce = NULL;
 	char digit = 0;
-#if 0	// BUGBUG
 	time_t callstart;
-#endif	// BUGBUG
 	time_t now = time(NULL);
 	struct ast_bridge_config bridge_config;
 	char nondataquality = 1;
@@ -5371,23 +5990,13 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a
 	char *macroexec = NULL;
 	char *gosubexec = NULL;
 	const char *monitorfilename;
-	const char *monitor_exec;
-	const char *monitor_options;
-	char tmpid[256], tmpid2[256];
-	char meid[1024], meid2[1024];
-	char mixmonargs[1512];
-	struct ast_app *mixmonapp = NULL;
-	char *p;
+	char tmpid[256];
+	char meid[1024];
 	int forwardsallowed = 1;
 	int block_connected_line = 0;
-#if 0	// BUGBUG
 	int callcompletedinsl;
-#endif	// BUGBUG
 	struct ao2_iterator memi;
 	struct ast_datastore *datastore;
-#if 0	// BUGBUG
-	struct ast_datastore *transfer_ds;
-#endif	// BUGBUG
 	struct queue_end_bridge *queue_end_bridge = NULL;
 
 	ast_channel_lock(qe->chan);
@@ -5654,9 +6263,7 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a
 		time(&now);
 		recalc_holdtime(qe, (now - qe->start));
 		ao2_lock(qe->parent);
-#if 0	// BUGBUG
 		callcompletedinsl = ((now - qe->start) <= qe->parent->servicelevel);
-#endif	// BUGBUG
 		ao2_unlock(qe->parent);
 		member = lpeer->member;
 		/* Increment the refcount for this member, since we're going to be using it for awhile in here. */
@@ -5810,96 +6417,7 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a
 					ast_monitor_setjoinfiles(which, 1);
 				}
 			} else {
-				mixmonapp = pbx_findapp("MixMonitor");
-
-				if (mixmonapp) {
-					ast_debug(1, "Starting MixMonitor as requested.\n");
-					if (!monitorfilename) {
-						if (qe->chan) {
-							ast_copy_string(tmpid, ast_channel_uniqueid(qe->chan), sizeof(tmpid));
-						} else {
-							snprintf(tmpid, sizeof(tmpid), "chan-%lx", ast_random());
-						}
-					} else {
-						const char *m = monitorfilename;
-						for (p = tmpid2; p < tmpid2 + sizeof(tmpid2) - 1; p++, m++) {
-							switch (*m) {
-							case '^':
-								if (*(m + 1) == '{')
-									*p = '$';
-								break;
-							case ',':
-								*p++ = '\\';
-								/* Fall through */
-							default:
-								*p = *m;
-							}
-							if (*m == '\0')
-								break;
-						}
-						if (p == tmpid2 + sizeof(tmpid2))
-							tmpid2[sizeof(tmpid2) - 1] = '\0';
-
-						pbx_substitute_variables_helper(qe->chan, tmpid2, tmpid, sizeof(tmpid) - 1);
-					}
-
-					ast_channel_lock(qe->chan);
-					if ((monitor_exec = pbx_builtin_getvar_helper(qe->chan, "MONITOR_EXEC"))) {
-							monitor_exec = ast_strdupa(monitor_exec);
-					}
-					if ((monitor_options = pbx_builtin_getvar_helper(qe->chan, "MONITOR_OPTIONS"))) {
-							monitor_options = ast_strdupa(monitor_options);
-					} else {
-						monitor_options = "";
-					}
-					ast_channel_unlock(qe->chan);
-
-					if (monitor_exec) {
-						const char *m = monitor_exec;
-						for (p = meid2; p < meid2 + sizeof(meid2) - 1; p++, m++) {
-							switch (*m) {
-							case '^':
-								if (*(m + 1) == '{')
-									*p = '$';
-								break;
-							case ',':
-								*p++ = '\\';
-								/* Fall through */
-							default:
-								*p = *m;
-							}
-							if (*m == '\0') {
-								break;
-							}
-						}
-						if (p == meid2 + sizeof(meid2)) {
-							meid2[sizeof(meid2) - 1] = '\0';
-						}
-
-						pbx_substitute_variables_helper(qe->chan, meid2, meid, sizeof(meid) - 1);
-					}
-
-					snprintf(tmpid2, sizeof(tmpid2), "%s.%s", tmpid, qe->parent->monfmt);
-
-					if (!ast_strlen_zero(monitor_exec)) {
-						snprintf(mixmonargs, sizeof(mixmonargs), "%s,b%s,%s", tmpid2, monitor_options, monitor_exec);
-					} else {
-						snprintf(mixmonargs, sizeof(mixmonargs), "%s,b%s", tmpid2, monitor_options);
-					}
-
-					ast_debug(1, "Arguments being passed to MixMonitor: %s\n", mixmonargs);
-					/* BUGBUG
-					 * This needs to be done differently. We need to start a MixMonitor on
-					 * the actual queue bridge itself, not drop some channel out and pull it
-					 * back. Once the media channel work is done, start a media channel on
-					 * the bridge.
-					 *
-					 * Alternatively, don't use pbx_exec to put an audio hook on a channel.
-					 */
-					pbx_exec(qe->chan, mixmonapp, mixmonargs);
-				} else {
-					ast_log(LOG_WARNING, "Asked to run MixMonitor on this call, but cannot find the MixMonitor app!\n");
-				}
+				setup_mixmonitor(qe, monitorfilename);
 			}
 		}
 		/* Drop out of the queue at this point, to prepare for next caller */
@@ -6008,53 +6526,10 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a
 			queue_t_ref(qe->parent, "For bridge_config reference");
 		}
 
-#if 0	// BUGBUG
 		time(&callstart);
-		transfer_ds = setup_transfer_datastore(qe, member, callstart, callcompletedinsl);
-#endif	// BUGBUG
-		bridge = ast_bridge_call(qe->chan, peer, &bridge_config);
-
-/* BUGBUG need to do this queue logging a different way because we cannot reference peer anymore.  Likely needs to be made a subscriber of stasis transfer events. */
-#if 0	// BUGBUG
-		/* If the queue member did an attended transfer, then the TRANSFER already was logged in the queue_log
-		 * when the masquerade occurred. These other "ending" queue_log messages are unnecessary, except for
-		 * the AgentComplete manager event
-		 */
-		ast_channel_lock(qe->chan);
-		if (!attended_transfer_occurred(qe->chan)) {
-			struct ast_datastore *tds;
-
-			/* detect a blind transfer */
-			if (!(ast_channel_softhangup_internal_flag(qe->chan) | ast_channel_softhangup_internal_flag(peer)) && (strcasecmp(oldcontext, ast_channel_context(qe->chan)) || strcasecmp(oldexten, ast_channel_exten(qe->chan)))) {
-				ast_queue_log(queuename, ast_channel_uniqueid(qe->chan), member->membername, "TRANSFER", "%s|%s|%ld|%ld|%d",
-					ast_channel_exten(qe->chan), ast_channel_context(qe->chan), (long) (callstart - qe->start),
-					(long) (time(NULL) - callstart), qe->opos);
-				send_agent_complete(qe, queuename, peer, member, callstart, vars, sizeof(vars), TRANSFER);
-			} else if (ast_check_hangup(qe->chan) && !ast_check_hangup(peer)) {
-				ast_queue_log(queuename, ast_channel_uniqueid(qe->chan), member->membername, "COMPLETECALLER", "%ld|%ld|%d",
-					(long) (callstart - qe->start), (long) (time(NULL) - callstart), qe->opos);
-				send_agent_complete(qe, queuename, peer, member, callstart, vars, sizeof(vars), CALLER);
-			} else {
-				ast_queue_log(queuename, ast_channel_uniqueid(qe->chan), member->membername, "COMPLETEAGENT", "%ld|%ld|%d",
-					(long) (callstart - qe->start), (long) (time(NULL) - callstart), qe->opos);
-				send_agent_complete(qe, queuename, peer, member, callstart, vars, sizeof(vars), AGENT);
-			}
-			if ((tds = ast_channel_datastore_find(qe->chan, &queue_transfer_info, NULL))) {
-				ast_channel_datastore_remove(qe->chan, tds);
-			}
-			ast_channel_unlock(qe->chan);
-			update_queue(qe->parent, member, callcompletedinsl, (time(NULL) - callstart));
-		} else {
-			ast_channel_unlock(qe->chan);
-
-			/* We already logged the TRANSFER on the queue_log, but we still need to send the AgentComplete event */
-			send_agent_complete(qe, queuename, peer, member, callstart, vars, sizeof(vars), TRANSFER);
-		}
-
-		if (transfer_ds) {
-			ast_datastore_free(transfer_ds);
-		}
-#endif	// BUGBUG
+		setup_stasis_subs(qe, peer, member, qe->start, callstart, callcompletedinsl);
+		bridge = ast_bridge_call_with_flags(qe->chan, peer, &bridge_config,
+				AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM | AST_BRIDGE_FLAG_MERGE_INHIBIT_TO | AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM);
 
 		res = bridge ? bridge : 1;
 		ao2_ref(member, -1);
@@ -9835,6 +10310,9 @@ static const struct ast_data_entry queue_data_providers[] = {
 	AST_DATA_ENTRY("asterisk/application/queue/list", &queues_data_provider),
 };
 
+static struct stasis_message_router *agent_router;
+static struct stasis_subscription *topic_forwarder;
+
 static int unload_module(void)
 {
 	int res;
@@ -9860,6 +10338,8 @@ static int unload_module(void)
 		stasis_message_router_remove(message_router, queue_agent_dump_type());
 		stasis_message_router_remove(message_router, queue_agent_ringnoanswer_type());
 	}
+	stasis_message_router_unsubscribe_and_join(agent_router);
+	topic_forwarder = stasis_unsubscribe(topic_forwarder);
 
 	STASIS_MESSAGE_TYPE_CLEANUP(queue_caller_join_type);
 	STASIS_MESSAGE_TYPE_CLEANUP(queue_caller_leave_type);
@@ -9937,7 +10417,9 @@ static int load_module(void)
 	int res;
 	struct ast_flags mask = {AST_FLAGS_ALL, };
 	struct ast_config *member_config;
-	struct stasis_message_router *message_router;
+	struct stasis_message_router *manager_router;
+	struct stasis_topic *queue_topic;
+	struct stasis_topic *manager_topic;
 
 	queues = ao2_container_alloc(MAX_QUEUE_BUCKETS, queue_hash_cb, queue_cmp_cb);
 
@@ -10008,8 +10490,25 @@ static int load_module(void)
 		res = -1;
 	}
 
-	message_router = ast_manager_get_message_router();
-	if (!message_router) {
+	manager_topic = ast_manager_get_topic();
+	if (!manager_topic) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+	manager_router = ast_manager_get_message_router();
+	if (!manager_router) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+	queue_topic = ast_queue_topic_all();
+	if (!queue_topic) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+	topic_forwarder = stasis_forward_all(queue_topic, manager_topic);
+	if (!topic_forwarder) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	agent_router = stasis_message_router_create(ast_channel_topic_all());
+	if (!agent_router) {
 		return AST_MODULE_LOAD_DECLINE;
 	}
 
@@ -10030,76 +10529,86 @@ static int load_module(void)
 	STASIS_MESSAGE_TYPE_INIT(queue_agent_dump_type);
 	STASIS_MESSAGE_TYPE_INIT(queue_agent_ringnoanswer_type);
 
-	stasis_message_router_add(message_router,
+	stasis_message_router_add(manager_router,
 				  queue_caller_join_type(),
 				  queue_channel_manager_event,
 				  "QueueCallerJoin");
 
-	stasis_message_router_add(message_router,
+	stasis_message_router_add(manager_router,
 				  queue_caller_leave_type(),
 				  queue_channel_manager_event,
 				  "QueueCallerLeave");
 
-	stasis_message_router_add(message_router,
+	stasis_message_router_add(manager_router,
 				  queue_caller_abandon_type(),
 				  queue_channel_manager_event,
 				  "QueueCallerAbandon");
 
-	stasis_message_router_add(message_router,
+	stasis_message_router_add(manager_router,
 				  queue_member_status_type(),
 				  queue_member_manager_event,
 				  "QueueMemberStatus");
 
-	stasis_message_router_add(message_router,
+	stasis_message_router_add(manager_router,
 				  queue_member_added_type(),
 				  queue_member_manager_event,
 				  "QueueMemberAdded");
 
-	stasis_message_router_add(message_router,
+	stasis_message_router_add(manager_router,
 				  queue_member_removed_type(),
 				  queue_member_manager_event,
 				  "QueueMemberRemoved");
 
-	stasis_message_router_add(message_router,
+	stasis_message_router_add(manager_router,
 				  queue_member_pause_type(),
 				  queue_member_manager_event,
 				  "QueueMemberPause");
 
-	stasis_message_router_add(message_router,
+	stasis_message_router_add(manager_router,
 				  queue_member_penalty_type(),
 				  queue_member_manager_event,
 				  "QueueMemberPenalty");
 
-	stasis_message_router_add(message_router,
+	stasis_message_router_add(manager_router,
 				  queue_member_ringinuse_type(),
 				  queue_member_manager_event,
 				  "QueueMemberRinginuse");
 
-	stasis_message_router_add(message_router,
+	stasis_message_router_add(manager_router,
 				  queue_agent_called_type(),
 				  queue_multi_channel_manager_event,
 				  "AgentCalled");
 
-	stasis_message_router_add(message_router,
+	stasis_message_router_add(manager_router,
 				  queue_agent_connect_type(),
 				  queue_multi_channel_manager_event,
 				  "AgentConnect");
 
-	stasis_message_router_add(message_router,
+	stasis_message_router_add(manager_router,
 				  queue_agent_complete_type(),
 				  queue_multi_channel_manager_event,
 				  "AgentComplete");
 
-	stasis_message_router_add(message_router,
+	stasis_message_router_add(manager_router,
 				  queue_agent_dump_type(),
 				  queue_multi_channel_manager_event,
 				  "AgentDump");
 
-	stasis_message_router_add(message_router,
+	stasis_message_router_add(manager_router,
 				  queue_agent_ringnoanswer_type(),
 				  queue_multi_channel_manager_event,
 				  "AgentRingNoAnswer");
 
+	stasis_message_router_add(agent_router,
+			ast_channel_agent_login_type(),
+			queue_agent_cb,
+			NULL);
+
+	stasis_message_router_add(agent_router,
+			ast_channel_agent_logoff_type(),
+			queue_agent_cb,
+			NULL);
+
 	ast_extension_state_add(NULL, NULL, extension_state_cb, NULL);
 
 	return res ? AST_MODULE_LOAD_DECLINE : 0;
diff --git a/include/asterisk/app.h b/include/asterisk/app.h
index 06b903e2f352332eae99131508bbbc891aca4bb3..c30290f6ecfed43f45803c80bd90d4dbca1dd4b6 100644
--- a/include/asterisk/app.h
+++ b/include/asterisk/app.h
@@ -1284,6 +1284,22 @@ struct stasis_message_type *ast_mwi_state_type(void);
  */
 struct stasis_message_type *ast_mwi_vm_app_type(void);
 
+/*!
+ * \brief Get the \ref stasis topic for queue messages
+ * \retval The topic structure for queue messages
+ * \retval NULL if it has not been allocated
+ * \since 12
+ */
+struct stasis_topic *ast_queue_topic_all(void);
+
+/*!
+ * \brief Get the \ref stasis topic for queue messages for a particular queue name
+ * \param queuename The name for which to get the topic
+ * \retval The topic structure for queue messages for a given name
+ * \retval NULL if it failed to be found or allocated
+ * \since 12
+ */
+struct stasis_topic *ast_queue_topic(const char *queuename);
 /*! @} */
 
 /*!
diff --git a/include/asterisk/bridge_basic.h b/include/asterisk/bridge_basic.h
index 560e94d5fc321b86ec4ead71016b063823deca7d..483bf230e0fc5f425d5c6ed66feddfe3583a3444 100644
--- a/include/asterisk/bridge_basic.h
+++ b/include/asterisk/bridge_basic.h
@@ -33,6 +33,7 @@
 extern "C" {
 #endif
 
+#define AST_TRANSFERER_ROLE_NAME "transferer"
 /* ------------------------------------------------------------------- */
 
 /*!
@@ -126,6 +127,17 @@ extern struct ast_bridge_methods ast_bridge_basic_v_table;
  */
 struct ast_bridge *ast_bridge_basic_new(void);
 
+/*!
+ * \brief Set feature flags on a basic bridge
+ *
+ * Using this function instead of setting flags directly will
+ * ensure that after operations such as an attended transfer,
+ * the bridge will maintain the flags that were set on it.
+ *
+ * \params Flags to set on the bridge. These are added to the flags already set.
+ */
+void ast_bridge_basic_set_flags(struct ast_bridge *bridge, unsigned int flags);
+
 /*! Initialize the basic bridge class for use by the system. */
 void ast_bridging_init_basic(void);
 
diff --git a/include/asterisk/core_unreal.h b/include/asterisk/core_unreal.h
index 9a8d2e1f54a1d97add7fb266b3b895ad866cb8a3..cd8a2cdf78d9bce59366d2eb72308d32dd268cc4 100644
--- a/include/asterisk/core_unreal.h
+++ b/include/asterisk/core_unreal.h
@@ -46,6 +46,11 @@ struct ast_callid;
 
 struct ast_unreal_pvt;
 
+enum ast_unreal_channel_indicator {
+	AST_UNREAL_OWNER,
+	AST_UNREAL_CHAN,
+};
+
 /*!
  * \brief Callbacks that can be provided by concrete implementations of the unreal
  * channel driver that will be called when events occur in the unreal layer
@@ -55,8 +60,14 @@ struct ast_unreal_pvt_callbacks {
 	 * \brief Called when an optimization attempt has started
 	 * \note p is locked when this callback is called
 	 * \param p The \ref ast_unreal_pvt object
+	 * \param source The channel that is optimizing into an unreal_pvt channel's bridge.
+	 * If NULL, the optimization is being accomplished via a bridge merge.
+	 * \param dest Indicator of which channel's bridge in the unreal_pvt will survive the
+	 * optimization
+	 * \param id Unique identifier for this optimization operation.
 	 */
-	void (* const optimization_started)(struct ast_unreal_pvt *p);
+	void (* const optimization_started)(struct ast_unreal_pvt *p, struct ast_channel *source,
+			enum ast_unreal_channel_indicator dest, unsigned int id);
 
 	/*!
 	 * \brief Called when an optimization attempt completed successfully
@@ -64,8 +75,10 @@ struct ast_unreal_pvt_callbacks {
 	 * \param p The \ref ast_unreal_pvt object
 	 * \param success Non-zero if the optimization succeeded, zero if the optimization
 	 * met with fatal and permanent error
+	 * \param id Unique identifier for this optimization. Same as the one from the optimization_started
+	 * call
 	 */
-	void (* const optimization_finished)(struct ast_unreal_pvt *p);
+	void (* const optimization_finished)(struct ast_unreal_pvt *p, int success, unsigned int id);
 };
 
 /*!
diff --git a/include/asterisk/features.h b/include/asterisk/features.h
index 9430bc4cbe4162482723e67da77ea989cb570c23..af9b77749b36be3a5268e9ad01f892a38253729e 100644
--- a/include/asterisk/features.h
+++ b/include/asterisk/features.h
@@ -42,6 +42,20 @@ enum {
 /*! \brief Bridge a call, optionally allowing redirection */
 int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer,struct ast_bridge_config *config);
 
+/*!
+ * \brief Bridge a call, and add additional flags to the bridge
+ *
+ * This does the same thing as \ref ast_bridge_call, except that once the bridge
+ * is created, the provided flags are set on the bridge. The provided flags are
+ * added to the bridge's flags; they will not clear any flags already set.
+ *
+ * \param chan The calling channel
+ * \param peer The called channel
+ * \param config Bridge configuration for the channels
+ * \param flags Additional flags to set on the created bridge
+ */
+int ast_bridge_call_with_flags(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, unsigned int flags);
+
 /*!
  * \brief Add an arbitrary channel to a bridge
  * \since 12.0.0
diff --git a/main/app.c b/main/app.c
index ee2bbf467e27908e30dcc7feb74c880dee2b7579..9410f9813414ed57b66da5e843a05af845c1d877 100644
--- a/main/app.c
+++ b/main/app.c
@@ -85,12 +85,15 @@ struct zombie {
 static AST_LIST_HEAD_STATIC(zombies, zombie);
 
 /*
- * @{ \brief Define \ref stasis topic objects for MWI
+ * @{ \brief Define \ref stasis topic objects
  */
 static struct stasis_topic *mwi_topic_all;
 static struct stasis_cache *mwi_state_cache;
 static struct stasis_caching_topic *mwi_topic_cached;
 static struct stasis_topic_pool *mwi_topic_pool;
+
+static struct stasis_topic *queue_topic_all;
+static struct stasis_topic_pool *queue_topic_pool;
 /* @} */
 
 /*
@@ -2978,8 +2981,22 @@ struct stasis_message *ast_mwi_blob_create(struct ast_mwi_state *mwi_state,
 	return msg;
 }
 
+struct stasis_topic *ast_queue_topic_all(void)
+{
+	return queue_topic_all;
+}
+
+struct stasis_topic *ast_queue_topic(const char *queuename)
+{
+	return stasis_topic_pool_get_topic(queue_topic_pool, queuename);
+}
+
 static void app_cleanup(void)
 {
+	ao2_cleanup(queue_topic_pool);
+	queue_topic_pool = NULL;
+	ao2_cleanup(queue_topic_all);
+	queue_topic_all = NULL;
 	ao2_cleanup(mwi_topic_pool);
 	mwi_topic_pool = NULL;
 	ao2_cleanup(mwi_topic_all);
@@ -3015,7 +3032,14 @@ int app_init(void)
 	if (!mwi_topic_pool) {
 		return -1;
 	}
-
+	queue_topic_all = stasis_topic_create("stasis_queue_topic");
+	if (!queue_topic_all) {
+		return -1;
+	}
+	queue_topic_pool = stasis_topic_pool_create(queue_topic_all);
+	if (!queue_topic_pool) {
+		return -1;
+	}
 	return 0;
 }
 
diff --git a/main/bridge.c b/main/bridge.c
index 30b3c0ed4db00b449cba88756a04f2fc059e2dce..e472bfcd306613a93fcae6b4df9c2b4fa197911e 100644
--- a/main/bridge.c
+++ b/main/bridge.c
@@ -72,6 +72,8 @@ static struct ao2_container *bridges;
 
 static AST_RWLIST_HEAD_STATIC(bridge_technologies, ast_bridge_technology);
 
+static unsigned int optimization_id;
+
 /* Initial starting point for the bridge array of channels */
 #define BRIDGE_ARRAY_START 128
 
@@ -2435,22 +2437,26 @@ static int try_swap_optimize_out(struct ast_bridge *chan_bridge,
 
 	other = ast_bridge_channel_peer(src_bridge_channel);
 	if (other && other->state == BRIDGE_CHANNEL_STATE_WAIT) {
+		unsigned int id = ast_atomic_fetchadd_int((int *) &optimization_id, +1);
+
 		ast_verb(3, "Move-swap optimizing %s <-- %s.\n",
 			ast_channel_name(dst_bridge_channel->chan),
 			ast_channel_name(other->chan));
 
 		if (pvt && !ast_test_flag(pvt, AST_UNREAL_OPTIMIZE_BEGUN) && pvt->callbacks
 				&& pvt->callbacks->optimization_started) {
-			pvt->callbacks->optimization_started(pvt);
+			pvt->callbacks->optimization_started(pvt, other->chan,
+					dst_bridge_channel->chan == pvt->owner ? AST_UNREAL_OWNER : AST_UNREAL_CHAN,
+					id);
 			ast_set_flag(pvt, AST_UNREAL_OPTIMIZE_BEGUN);
 		}
 		other->swap = dst_bridge_channel->chan;
 		if (!bridge_do_move(dst_bridge, other, 1, 1)) {
 			ast_bridge_channel_leave_bridge(src_bridge_channel, BRIDGE_CHANNEL_STATE_END_NO_DISSOLVE);
 			res = -1;
-			if (pvt && pvt->callbacks && pvt->callbacks->optimization_finished) {
-				pvt->callbacks->optimization_finished(pvt);
-			}
+		}
+		if (pvt && pvt->callbacks && pvt->callbacks->optimization_finished) {
+			pvt->callbacks->optimization_finished(pvt, res == 1, id);
 		}
 	}
 	return res;
@@ -2528,6 +2534,7 @@ static int try_merge_optimize_out(struct ast_bridge *chan_bridge,
 		chan_bridge_channel,
 		peer_bridge_channel,
 	};
+	unsigned int id;
 
 	switch (bridges_allow_merge_optimization(chan_bridge, peer_bridge, ARRAY_LEN(kick_me), &merge)) {
 	case MERGE_ALLOWED:
@@ -2551,14 +2558,18 @@ static int try_merge_optimize_out(struct ast_bridge *chan_bridge,
 		ast_channel_name(chan_bridge_channel->chan),
 		ast_channel_name(peer_bridge_channel->chan));
 
+	id = ast_atomic_fetchadd_int((int *) &optimization_id, +1);
+
 	if (pvt && !ast_test_flag(pvt, AST_UNREAL_OPTIMIZE_BEGUN) && pvt->callbacks
 			&& pvt->callbacks->optimization_started) {
-		pvt->callbacks->optimization_started(pvt);
+		pvt->callbacks->optimization_started(pvt, NULL,
+				merge.dest == ast_channel_internal_bridge(pvt->owner) ? AST_UNREAL_OWNER : AST_UNREAL_CHAN,
+				id);
 		ast_set_flag(pvt, AST_UNREAL_OPTIMIZE_BEGUN);
 	}
 	bridge_do_merge(merge.dest, merge.src, kick_me, ARRAY_LEN(kick_me), 1);
 	if (pvt && pvt->callbacks && pvt->callbacks->optimization_finished) {
-		pvt->callbacks->optimization_finished(pvt);
+		pvt->callbacks->optimization_finished(pvt, 1, id);
 	}
 
 	return -1;
diff --git a/main/bridge_basic.c b/main/bridge_basic.c
index 70556d748fb2936072dc5395dd45631e4f37323f..283387a422f62ec65caabac9d275371c8c0b458a 100644
--- a/main/bridge_basic.c
+++ b/main/bridge_basic.c
@@ -51,7 +51,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 			| AST_BRIDGE_FLAG_SMART)
 
 #define TRANSFER_FLAGS AST_BRIDGE_FLAG_SMART
-#define TRANSFERER_ROLE_NAME "transferer"
 
 struct attended_transfer_properties;
 
@@ -1494,7 +1493,7 @@ static void attended_transfer_properties_shutdown(struct attended_transfer_prope
 	}
 
 	if (props->transferer) {
-		ast_channel_remove_bridge_role(props->transferer, TRANSFERER_ROLE_NAME);
+		ast_channel_remove_bridge_role(props->transferer, AST_TRANSFERER_ROLE_NAME);
 	}
 
 	clear_stimulus_queue(props);
@@ -2581,14 +2580,14 @@ static int bridge_personality_atxfer_push(struct ast_bridge *self, struct ast_br
 	const char *swap_dtmf;
 	struct bridge_basic_personality *personality = self->personality;
 
-	if (!ast_channel_has_role(bridge_channel->chan, TRANSFERER_ROLE_NAME)) {
+	if (!ast_channel_has_role(bridge_channel->chan, AST_TRANSFERER_ROLE_NAME)) {
 		return 0;
 	}
 
-	abort_dtmf = ast_channel_get_role_option(bridge_channel->chan, TRANSFERER_ROLE_NAME, "abort");
-	complete_dtmf = ast_channel_get_role_option(bridge_channel->chan, TRANSFERER_ROLE_NAME, "complete");
-	threeway_dtmf = ast_channel_get_role_option(bridge_channel->chan, TRANSFERER_ROLE_NAME, "threeway");
-	swap_dtmf = ast_channel_get_role_option(bridge_channel->chan, TRANSFERER_ROLE_NAME, "swap");
+	abort_dtmf = ast_channel_get_role_option(bridge_channel->chan, AST_TRANSFERER_ROLE_NAME, "abort");
+	complete_dtmf = ast_channel_get_role_option(bridge_channel->chan, AST_TRANSFERER_ROLE_NAME, "complete");
+	threeway_dtmf = ast_channel_get_role_option(bridge_channel->chan, AST_TRANSFERER_ROLE_NAME, "threeway");
+	swap_dtmf = ast_channel_get_role_option(bridge_channel->chan, AST_TRANSFERER_ROLE_NAME, "swap");
 
 	if (!ast_strlen_zero(abort_dtmf) && ast_bridge_dtmf_hook(bridge_channel->features,
 			abort_dtmf, atxfer_abort, personality->details[personality->current].pvt, NULL,
@@ -2838,11 +2837,11 @@ static int add_transferer_role(struct ast_channel *chan, struct ast_bridge_featu
 		atxfer_swap = ast_strdupa(xfer_cfg->atxferswap);
 	}
 
-	return ast_channel_add_bridge_role(chan, TRANSFERER_ROLE_NAME) ||
-		ast_channel_set_bridge_role_option(chan, TRANSFERER_ROLE_NAME, "abort", atxfer_abort) ||
-		ast_channel_set_bridge_role_option(chan, TRANSFERER_ROLE_NAME, "complete", atxfer_complete) ||
-		ast_channel_set_bridge_role_option(chan, TRANSFERER_ROLE_NAME, "threeway", atxfer_threeway) ||
-		ast_channel_set_bridge_role_option(chan, TRANSFERER_ROLE_NAME, "swap", atxfer_swap);
+	return ast_channel_add_bridge_role(chan, AST_TRANSFERER_ROLE_NAME) ||
+		ast_channel_set_bridge_role_option(chan, AST_TRANSFERER_ROLE_NAME, "abort", atxfer_abort) ||
+		ast_channel_set_bridge_role_option(chan, AST_TRANSFERER_ROLE_NAME, "complete", atxfer_complete) ||
+		ast_channel_set_bridge_role_option(chan, AST_TRANSFERER_ROLE_NAME, "threeway", atxfer_threeway) ||
+		ast_channel_set_bridge_role_option(chan, AST_TRANSFERER_ROLE_NAME, "swap", atxfer_swap);
 }
 
 /*!
@@ -3243,6 +3242,15 @@ struct ast_bridge *ast_bridge_basic_new(void)
 	return bridge;
 }
 
+void ast_bridge_basic_set_flags(struct ast_bridge *bridge, unsigned int flags)
+{
+	SCOPED_LOCK(lock, bridge, ast_bridge_lock, ast_bridge_unlock);
+	struct bridge_basic_personality *personality = bridge->personality;
+
+	personality->details[personality->current].bridge_flags |= flags;
+	ast_set_flag(&bridge->feature_flags, flags);
+}
+
 void ast_bridging_init_basic(void)
 {
 	/* Setup bridge basic subclass v_table. */
diff --git a/main/core_local.c b/main/core_local.c
index 3cdff9503c53adca35addb69b2a2bb88529cd595..8825739a6dd4549cfad1ab3117bd42a9a305b685 100644
--- a/main/core_local.c
+++ b/main/core_local.c
@@ -96,6 +96,13 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 			<syntax>
 				<channel_snapshot prefix="LocalOne"/>
 				<channel_snapshot prefix="LocalTwo"/>
+				<channel_snapshot prefix="Source"/>
+				<parameter name="DestUniqueId">
+					<para>The unique ID of the bridge into which the local channel is optimizing.</para>
+				</parameter>
+				<parameter name="Id">
+					<para>Identification for the optimization operation.</para>
+				</parameter>
 			</syntax>
 			<see-also>
 				<ref type="managerEvent">LocalOptimizationEnd</ref>
@@ -110,6 +117,13 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 			<syntax>
 				<channel_snapshot prefix="LocalOne"/>
 				<channel_snapshot prefix="LocalTwo"/>
+				<parameter name="Success">
+					<para>Indicates whether the local optimization succeeded.</para>
+				</parameter>
+				<parameter name="Id">
+					<para>Identification for the optimization operation. Matches the <replaceable>Id</replaceable>
+					from a previous <literal>LocalOptimizationBegin</literal></para>
+				</parameter>
 			</syntax>
 			<see-also>
 				<ref type="managerEvent">LocalOptimizationBegin</ref>
@@ -127,8 +141,9 @@ static struct ast_channel *local_request(const char *type, struct ast_format_cap
 static int local_call(struct ast_channel *ast, const char *dest, int timeout);
 static int local_hangup(struct ast_channel *ast);
 static int local_devicestate(const char *data);
-static void local_optimization_started_cb(struct ast_unreal_pvt *base);
-static void local_optimization_finished_cb(struct ast_unreal_pvt *base);
+static void local_optimization_started_cb(struct ast_unreal_pvt *base, struct ast_channel *source,
+		enum ast_unreal_channel_indicator dest, unsigned int id);
+static void local_optimization_finished_cb(struct ast_unreal_pvt *base, int success, unsigned int id);
 
 static struct ast_manager_event_blob *local_message_to_ami(struct stasis_message *msg);
 
@@ -306,58 +321,97 @@ static int local_devicestate(const char *data)
 	return res;
 }
 
-static void publish_local_optimization(struct local_pvt *p, int complete)
+static struct ast_multi_channel_blob *local_channel_optimization_blob(struct local_pvt *p,
+		struct ast_json *json_object)
 {
-	RAII_VAR(struct ast_multi_channel_blob *, payload, NULL, ao2_cleanup);
-	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
-	RAII_VAR(struct ast_json *, blob, ast_json_null(), ast_json_unref);
+	struct ast_multi_channel_blob *payload;
 	RAII_VAR(struct ast_channel_snapshot *, local_one_snapshot, NULL, ao2_cleanup);
 	RAII_VAR(struct ast_channel_snapshot *, local_two_snapshot, NULL, ao2_cleanup);
 
-	if (!blob) {
-		return;
-	}
-
 	local_one_snapshot = ast_channel_snapshot_create(p->base.owner);
 	if (!local_one_snapshot) {
-		return;
+		return NULL;
 	}
 
 	local_two_snapshot = ast_channel_snapshot_create(p->base.chan);
 	if (!local_two_snapshot) {
-		return;
+		return NULL;
 	}
 
-	payload = ast_multi_channel_blob_create(blob);
+	payload = ast_multi_channel_blob_create(json_object);
 	if (!payload) {
-		return;
+		return NULL;
 	}
 	ast_multi_channel_blob_add_channel(payload, "1", local_one_snapshot);
 	ast_multi_channel_blob_add_channel(payload, "2", local_two_snapshot);
 
-	msg = stasis_message_create(
-			complete ? ast_local_optimization_end_type() : ast_local_optimization_begin_type(),
-			payload);
-	if (!msg) {
-		return;
-	}
-
-	stasis_publish(ast_channel_topic(p->base.owner), msg);
-
+	return payload;
 }
 
 /*! \brief Callback for \ref ast_unreal_pvt_callbacks \ref optimization_started_cb */
-static void local_optimization_started_cb(struct ast_unreal_pvt *base)
+static void local_optimization_started_cb(struct ast_unreal_pvt *base, struct ast_channel *source,
+		enum ast_unreal_channel_indicator dest, unsigned int id)
 {
+	RAII_VAR(struct ast_json *, json_object, ast_json_null(), ast_json_unref);
+	RAII_VAR(struct ast_multi_channel_blob *, payload, NULL, ao2_cleanup);
+	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
 	struct local_pvt *p = (struct local_pvt *)base;
-	publish_local_optimization(p, 0);
+
+	json_object = ast_json_pack("{s: i, s: i}",
+			"dest", dest, "id", id);
+
+	if (!json_object) {
+		return;
+	}
+
+	payload = local_channel_optimization_blob(p, json_object);
+	if (!payload) {
+		return;
+	}
+
+	if (source) {
+		RAII_VAR(struct ast_channel_snapshot *, source_snapshot, NULL, ao2_cleanup);
+		source_snapshot = ast_channel_snapshot_create(source);
+		if (!source_snapshot) {
+			return;
+		}
+
+		ast_multi_channel_blob_add_channel(payload, "source", source_snapshot);
+	}
+
+	msg = stasis_message_create(ast_local_optimization_begin_type(), payload);
+	if (!msg) {
+		return;
+	}
+
+	stasis_publish(ast_channel_topic(p->base.owner), msg);
 }
 
 /*! \brief Callback for \ref ast_unreal_pvt_callbacks \ref optimization_finished_cb */
-static void local_optimization_finished_cb(struct ast_unreal_pvt *base)
+static void local_optimization_finished_cb(struct ast_unreal_pvt *base, int success, unsigned int id)
 {
+	RAII_VAR(struct ast_json *, json_object, ast_json_null(), ast_json_unref);
+	RAII_VAR(struct ast_multi_channel_blob *, payload, NULL, ao2_cleanup);
+	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
 	struct local_pvt *p = (struct local_pvt *)base;
-	publish_local_optimization(p, 1);
+
+	json_object = ast_json_pack("{s: i, s: i}", "success", success, "id", id);
+
+	if (!json_object) {
+		return;
+	}
+
+	payload = local_channel_optimization_blob(p, json_object);
+	if (!payload) {
+		return;
+	}
+
+	msg = stasis_message_create(ast_local_optimization_end_type(), payload);
+	if (!msg) {
+		return;
+	}
+
+	stasis_publish(ast_channel_topic(p->base.owner), msg);
 }
 
 static struct ast_manager_event_blob *local_message_to_ami(struct stasis_message *message)
@@ -384,9 +438,31 @@ static struct ast_manager_event_blob *local_message_to_ami(struct stasis_message
 	}
 
 	if (stasis_message_type(message) == ast_local_optimization_begin_type()) {
+		struct ast_channel_snapshot *source_snapshot;
+		RAII_VAR(struct ast_str *, source_str, NULL, ast_free);
+		const char *dest_uniqueid;
+
+		source_snapshot = ast_multi_channel_blob_get_channel(obj, "source");
+		if (source_snapshot) {
+			source_str = ast_manager_build_channel_state_string_prefix(source_snapshot, "Source");
+			if (!source_str) {
+				return NULL;
+			}
+		}
+
+		dest_uniqueid = ast_json_object_get(blob, "dest") == AST_UNREAL_OWNER ?
+				local_snapshot_one->uniqueid : local_snapshot_two->uniqueid;
+
 		event = "LocalOptimizationBegin";
+		if (source_str) {
+			ast_str_append(&event_buffer, 0, "%s", ast_str_buffer(source_str));
+		}
+		ast_str_append(&event_buffer, 0, "DestUniqueId: %s\r\n", dest_uniqueid);
+		ast_str_append(&event_buffer, 0, "Id: %u\r\n", (unsigned int) ast_json_integer_get(ast_json_object_get(blob, "id")));
 	} else if (stasis_message_type(message) == ast_local_optimization_end_type()) {
 		event = "LocalOptimizationEnd";
+		ast_str_append(&event_buffer, 0, "Success: %s\r\n", ast_json_integer_get(ast_json_object_get(blob, "success")) ? "Yes" : "No");
+		ast_str_append(&event_buffer, 0, "Id: %u\r\n", (unsigned int) ast_json_integer_get(ast_json_object_get(blob, "id")));
 	} else if (stasis_message_type(message) == ast_local_bridge_type()) {
 		event = "LocalBridge";
 		ast_str_append(&event_buffer, 0, "Context: %s\r\n", ast_json_string_get(ast_json_object_get(blob, "context")));
diff --git a/main/features.c b/main/features.c
index 2601a4106cd091d9097513df9b38972eb579d073..f3e2f0fb16b396175977c2b0db24a8a9258b4e7d 100644
--- a/main/features.c
+++ b/main/features.c
@@ -639,19 +639,7 @@ static int pre_bridge_setup(struct ast_channel *chan, struct ast_channel *peer,
 	return 0;
 }
 
-/*!
- * \brief bridge the call and set CDR
- *
- * \param chan The bridge considers this channel the caller.
- * \param peer The bridge considers this channel the callee.
- * \param config Configuration for this bridge.
- *
- * Set start time, check for two channels,check if monitor on
- * check for feature activation, create new CDR
- * \retval res on success.
- * \retval -1 on failure to bridge.
- */
-int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config)
+int ast_bridge_call_with_flags(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, unsigned int flags)
 {
 	int res;
 	struct ast_bridge *bridge;
@@ -684,6 +672,8 @@ int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, struct a
 		return -1;
 	}
 
+	ast_bridge_basic_set_flags(bridge, flags);
+
 	/* Put peer into the bridge */
 	if (ast_bridge_impart(bridge, peer, NULL, peer_features, 1)) {
 		ast_bridge_destroy(bridge);
@@ -717,6 +707,23 @@ int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, struct a
 	return res;
 }
 
+/*!
+ * \brief bridge the call and set CDR
+ *
+ * \param chan The bridge considers this channel the caller.
+ * \param peer The bridge considers this channel the callee.
+ * \param config Configuration for this bridge.
+ *
+ * Set start time, check for two channels,check if monitor on
+ * check for feature activation, create new CDR
+ * \retval res on success.
+ * \retval -1 on failure to bridge.
+ */
+int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config)
+{
+	return ast_bridge_call_with_flags(chan, peer, config, 0);
+}
+
 enum play_tone_action {
 	PLAYTONE_NONE = 0,
 	PLAYTONE_CHANNEL1 = (1 << 0),