diff --git a/briar-api/src/org/briarproject/api/event/KeyAgreementAbortedEvent.java b/briar-api/src/org/briarproject/api/event/KeyAgreementAbortedEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..5e217f7f4d2752f09ae68d7e0ee9b9cbc68f9d46
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/event/KeyAgreementAbortedEvent.java
@@ -0,0 +1,15 @@
+package org.briarproject.api.event;
+
+/** An event that is broadcast when a BQP protocol aborts. */
+public class KeyAgreementAbortedEvent extends Event {
+
+	private final boolean remoteAborted;
+
+	public KeyAgreementAbortedEvent(boolean remoteAborted) {
+		this.remoteAborted = remoteAborted;
+	}
+
+	public boolean didRemoteAbort() {
+		return remoteAborted;
+	}
+}
diff --git a/briar-api/src/org/briarproject/api/event/KeyAgreementFailedEvent.java b/briar-api/src/org/briarproject/api/event/KeyAgreementFailedEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..97313a276b0bf19f582e4177b3680753ac09568f
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/event/KeyAgreementFailedEvent.java
@@ -0,0 +1,6 @@
+package org.briarproject.api.event;
+
+/** An event that is broadcast when a BQP connection cannot be created. */
+public class KeyAgreementFailedEvent extends Event {
+
+}
diff --git a/briar-api/src/org/briarproject/api/event/KeyAgreementFinishedEvent.java b/briar-api/src/org/briarproject/api/event/KeyAgreementFinishedEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..f2afe349ebb23c6a9c63d33d518660f7b84e036b
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/event/KeyAgreementFinishedEvent.java
@@ -0,0 +1,17 @@
+package org.briarproject.api.event;
+
+import org.briarproject.api.keyagreement.KeyAgreementResult;
+
+/** An event that is broadcast when a BQP protocol completes. */
+public class KeyAgreementFinishedEvent extends Event {
+
+	private final KeyAgreementResult result;
+
+	public KeyAgreementFinishedEvent(KeyAgreementResult result) {
+		this.result = result;
+	}
+
+	public KeyAgreementResult getResult() {
+		return result;
+	}
+}
diff --git a/briar-api/src/org/briarproject/api/event/KeyAgreementListeningEvent.java b/briar-api/src/org/briarproject/api/event/KeyAgreementListeningEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..df3b2cbac5fc6ced89e43a37cee90766a7a2e70b
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/event/KeyAgreementListeningEvent.java
@@ -0,0 +1,17 @@
+package org.briarproject.api.event;
+
+import org.briarproject.api.keyagreement.Payload;
+
+/** An event that is broadcast when a BQP task is listening. */
+public class KeyAgreementListeningEvent extends Event {
+
+	private final Payload localPayload;
+
+	public KeyAgreementListeningEvent(Payload localPayload) {
+		this.localPayload = localPayload;
+	}
+
+	public Payload getLocalPayload() {
+		return localPayload;
+	}
+}
diff --git a/briar-api/src/org/briarproject/api/event/KeyAgreementStartedEvent.java b/briar-api/src/org/briarproject/api/event/KeyAgreementStartedEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..a12a9c459a38c3b1131bb605f1b9eb857a13d2f2
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/event/KeyAgreementStartedEvent.java
@@ -0,0 +1,6 @@
+package org.briarproject.api.event;
+
+/** An event that is broadcast when a BQP protocol completes. */
+public class KeyAgreementStartedEvent extends Event {
+
+}
diff --git a/briar-api/src/org/briarproject/api/event/KeyAgreementWaitingEvent.java b/briar-api/src/org/briarproject/api/event/KeyAgreementWaitingEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..473c6538adb053d4aeb0551621ebf8a8b2c97aa2
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/event/KeyAgreementWaitingEvent.java
@@ -0,0 +1,9 @@
+package org.briarproject.api.event;
+
+/**
+ * An event that is broadcast when a BQP protocol is waiting on the remote
+ * peer to start.
+ */
+public class KeyAgreementWaitingEvent extends Event {
+
+}
diff --git a/briar-api/src/org/briarproject/api/keyagreement/KeyAgreementConstants.java b/briar-api/src/org/briarproject/api/keyagreement/KeyAgreementConstants.java
index 521789244d9a8e61ae5926ea24509ebde82fbf4f..f239111269124e8d938f15bbc5561a4670ed5d0a 100644
--- a/briar-api/src/org/briarproject/api/keyagreement/KeyAgreementConstants.java
+++ b/briar-api/src/org/briarproject/api/keyagreement/KeyAgreementConstants.java
@@ -3,6 +3,17 @@ package org.briarproject.api.keyagreement;
 
 public interface KeyAgreementConstants {
 
+	/** The current version of the BQP protocol. */
+	byte PROTOCOL_VERSION = 1;
+
+	/** The length of the record header in bytes. */
+	int RECORD_HEADER_LENGTH = 4;
+
+	/** The offset of the payload length in the record header, in bytes. */
+	int RECORD_HEADER_PAYLOAD_LENGTH_OFFSET = 2;
+
 	/** The length of the BQP key commitment in bytes. */
 	int COMMIT_LENGTH = 16;
+
+	long CONNECTION_TIMEOUT = 20 * 1000; // Milliseconds
 }
diff --git a/briar-api/src/org/briarproject/api/keyagreement/KeyAgreementResult.java b/briar-api/src/org/briarproject/api/keyagreement/KeyAgreementResult.java
new file mode 100644
index 0000000000000000000000000000000000000000..3f772ca28792d58571a82b5da7b6b65a1ab27624
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/keyagreement/KeyAgreementResult.java
@@ -0,0 +1,38 @@
+package org.briarproject.api.keyagreement;
+
+import org.briarproject.api.TransportId;
+import org.briarproject.api.crypto.SecretKey;
+import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
+
+public class KeyAgreementResult {
+
+	private final SecretKey masterKey;
+	private final DuplexTransportConnection connection;
+	private final TransportId transportId;
+	private final boolean alice;
+
+	public KeyAgreementResult(SecretKey masterKey,
+			DuplexTransportConnection connection, TransportId transportId,
+			boolean alice) {
+		this.masterKey = masterKey;
+		this.connection = connection;
+		this.transportId = transportId;
+		this.alice = alice;
+	}
+
+	public SecretKey getMasterKey() {
+		return masterKey;
+	}
+
+	public DuplexTransportConnection getConnection() {
+		return connection;
+	}
+
+	public TransportId getTransportId() {
+		return transportId;
+	}
+
+	public boolean wasAlice() {
+		return alice;
+	}
+}
diff --git a/briar-api/src/org/briarproject/api/keyagreement/KeyAgreementTask.java b/briar-api/src/org/briarproject/api/keyagreement/KeyAgreementTask.java
new file mode 100644
index 0000000000000000000000000000000000000000..443ef42bd68d11f7bae75cf576d8e2721bc0a63f
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/keyagreement/KeyAgreementTask.java
@@ -0,0 +1,21 @@
+package org.briarproject.api.keyagreement;
+
+/** A task for conducting a key agreement with a remote peer. */
+public interface KeyAgreementTask {
+
+	/**
+	 * Start listening for short-range BQP connections, if we are not already.
+	 * <p/>
+	 * Will trigger a KeyAgreementListeningEvent containing the local Payload,
+	 * even if we are already listening.
+	 */
+	void listen();
+
+	/**
+	 * Stop listening for short-range BQP connections.
+	 */
+	void stopListening();
+
+	/** Asynchronously start the connection process. */
+	void connectAndRunProtocol(Payload remotePayload);
+}
diff --git a/briar-api/src/org/briarproject/api/keyagreement/KeyAgreementTaskFactory.java b/briar-api/src/org/briarproject/api/keyagreement/KeyAgreementTaskFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..f823fcacdbfe44898399ad5851f62a86c542e06e
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/keyagreement/KeyAgreementTaskFactory.java
@@ -0,0 +1,8 @@
+package org.briarproject.api.keyagreement;
+
+/** Manages tasks for conducting key agreements with remote peers. */
+public interface KeyAgreementTaskFactory {
+
+	/** Gets the current key agreement task. */
+	KeyAgreementTask getTask();
+}
diff --git a/briar-api/src/org/briarproject/api/keyagreement/KeyAgreementTaskId.java b/briar-api/src/org/briarproject/api/keyagreement/KeyAgreementTaskId.java
new file mode 100644
index 0000000000000000000000000000000000000000..434f9cb71ac2ea2bd746fd6dd225d294b1f854bb
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/keyagreement/KeyAgreementTaskId.java
@@ -0,0 +1,18 @@
+package org.briarproject.api.keyagreement;
+
+import org.briarproject.api.UniqueId;
+
+/**
+ * Type-safe wrapper for a byte array that uniquely identifies a BQP task.
+ */
+public class KeyAgreementTaskId extends UniqueId {
+
+	public KeyAgreementTaskId(byte[] id) {
+		super(id);
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		return o instanceof KeyAgreementTaskId && super.equals(o);
+	}
+}
diff --git a/briar-api/src/org/briarproject/api/keyagreement/Payload.java b/briar-api/src/org/briarproject/api/keyagreement/Payload.java
new file mode 100644
index 0000000000000000000000000000000000000000..0c749da53ec9e2a70c652cc3dcdf70f5a9d079a6
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/keyagreement/Payload.java
@@ -0,0 +1,34 @@
+package org.briarproject.api.keyagreement;
+
+import org.briarproject.api.Bytes;
+
+import java.util.List;
+
+/**
+ * A BQP payload.
+ */
+public class Payload implements Comparable<Payload> {
+
+	private final Bytes commitment;
+	private final List<TransportDescriptor> descriptors;
+
+	public Payload(byte[] commitment, List<TransportDescriptor> descriptors) {
+		this.commitment = new Bytes(commitment);
+		this.descriptors = descriptors;
+	}
+
+	/** Returns the commitment contained in this payload. */
+	public byte[] getCommitment() {
+		return commitment.getBytes();
+	}
+
+	/** Returns the transport descriptors contained in this payload. */
+	public List<TransportDescriptor> getTransportDescriptors() {
+		return descriptors;
+	}
+
+	@Override
+	public int compareTo(Payload p) {
+		return commitment.compareTo(p.commitment);
+	}
+}
diff --git a/briar-api/src/org/briarproject/api/keyagreement/PayloadEncoder.java b/briar-api/src/org/briarproject/api/keyagreement/PayloadEncoder.java
new file mode 100644
index 0000000000000000000000000000000000000000..31876c10271146fe99de491bd6d3f1053ad769da
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/keyagreement/PayloadEncoder.java
@@ -0,0 +1,6 @@
+package org.briarproject.api.keyagreement;
+
+public interface PayloadEncoder {
+
+	byte[] encode(Payload p);
+}
diff --git a/briar-api/src/org/briarproject/api/keyagreement/PayloadParser.java b/briar-api/src/org/briarproject/api/keyagreement/PayloadParser.java
new file mode 100644
index 0000000000000000000000000000000000000000..0df9c653d418d464af843f74547981bc06f7a223
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/keyagreement/PayloadParser.java
@@ -0,0 +1,8 @@
+package org.briarproject.api.keyagreement;
+
+import java.io.IOException;
+
+public interface PayloadParser {
+
+	Payload parse(byte[] raw) throws IOException;
+}
diff --git a/briar-api/src/org/briarproject/api/keyagreement/RecordTypes.java b/briar-api/src/org/briarproject/api/keyagreement/RecordTypes.java
new file mode 100644
index 0000000000000000000000000000000000000000..ef1d2ff51feb21d04b9e995474e091aede397379
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/keyagreement/RecordTypes.java
@@ -0,0 +1,9 @@
+package org.briarproject.api.keyagreement;
+
+/** Record types for BQP. */
+public interface RecordTypes {
+
+	byte KEY = 0;
+	byte CONFIRM = 1;
+	byte ABORT = 2;
+}