diff --git a/docs/en/extn-developer-faq.md b/docs/en/extn-developer-faq.md
index 4cec64f7..0670a760 100644
--- a/docs/en/extn-developer-faq.md
+++ b/docs/en/extn-developer-faq.md
@@ -2,95 +2,85 @@
**# # # Work in Progress # # #**
-## How does the non-OS SDK structure execution
+### NodeMCU task paradigm
-Details of the execution model for the **non-OS SDK** is not well documented by
-Espressif. This section summarises the project's understanding of how this execution
-model works based on the Espressif-supplied examples and SDK documentation, plus
-various posts on the Espressif BBS and other forums, and an examination of the
-BootROM code.
+NodeMCU uses a task based approach for scheduling work to run within
+the Lua VM. This interface is defined in `task/task.h`, and comprises
+three aspects:
+ - Task registration (via `task_getid()`)
+ - Task posting (via `task_post()` and associated macros)
+ - Task processing (via `task_pump_messages()`)
-The ESP8266 boot ROM contains a set of primitive tasking and dispatch functions
-which are also used by the SDK. In this model, execution units are either:
+This NodeMCU task API is designed to complement the Lua library model, so
+that a library can declare one or more task handlers and that both ISRs
+and library functions can then post a message for delivery to a task handler.
-- **INTERRUPT SERVICE ROUTINES (ISRs)** which are declared and controlled
- through the `ets_isr_attach()` and other `ets_isr_*` and `ets_intr_*`
- functions. ISRs can be defined on a range of priorities, where a higher
- priority ISR is able to interrupt a lower priority one. ISRs are time
- critical and should complete in no more than 50 µSec.
+Note that NodeMCU tasks must not be confused with the FreeRTOS tasks.
+A FreeRTOS task is fully preemptible thread of execution managed by the
+OS, while a NodeMCU task is a non-preemptive\* a callback invoked by the
+Lua FreeRTOS task. It effectively implements cooperative multitasking.
+To reduce confusion, from here on FreeRTOS tasks will be referred to as
+threads, and NodeMCU tasks simply as tasks. Most NodeMCU developers
+will not need to concern themselves with threads unless they're doing
+low-level driver development.
- ISR code and data constants should be run out of RAM or ROM, for two reasons:
- if an ISR interrupts a flash I/O operation (which must disable the Flash
- instruction cache) and a cache miss occurs, then the ISR will trigger a
- fatal exception; secondly, the
- execution time for Flash memory (that is located in the `irom0` load section)
- is indeterminate: whilst cache-hits can run at full memory bandwidth, any
- cache-misses require the code to be read from Flash; and even though
- H/W-based, this is at roughly 26x slower than memory bandwidth (for DIO
- flash); this will cause ISR execution to fall outside the require time
- guidelines. (Note that any time critical code within normal execution and that
- is bracketed by interrupt lock / unlock guards should also follow this 50
- µSec guideline.)
-
-- **TASKS**. A task is a normal execution unit running at a non-interrupt priority.
- Tasks can be executed from Flash memory. An executing task can be interrupted
- by one or more ISRs being delivered, but it won't be preempted by another
- queued task. The Espressif guideline is that no individual task should run for
- more than 15 mSec, before returning control to the SDK.
+The Lua runtime is NOT reentrant, and hence any code which calls any
+Lua API must run within the Lua task context. _ISRs, other threads and API
+callbacks must not access the Lua API or Lua resources._ This typically
+means that messages need to be posted using the NodeMCU task API for
+handling within the correct thread context. Depending on the scenario,
+data may need to be put on a FreeRTOS queue to transfer ownership safely
+between the threads.
- The ROM will queue up to 32 pending tasks at priorities 0..31 and will
- execute the highest priority queued task next (or wait on interrupt if none
- is runnable). The SDK tasking system is layered on this ROM dispatcher and
- it reserves 29 of these task priorities for its own use, including the
- implementation of the various SDK timer, WiFi and other callback mechanisms
- such as the software WDT.
-
- Three of these task priorities are allocated for and exposed directly at an
- application level. The application can declare a single task handler for each
- level, and associate a task queue with the level. Tasks can be posted to this
- queue. (The post will fail is the queue is full). Tasks are then delivered
- FIFO within task priority.
-
- How the three user task priorities USER0 .. USER2 are positioned relative to
- the SDK task priorities is undocumented, but some SDK tasks definitely run at
- a lower priority than USER0. As a result if you always have a USER task queued
- for execution, then you can starve SDK housekeeping tasks and you will start
- to get WiFi and other failures. Espressif therefore recommends that you don't
- stay computable for more than 500 mSec to avoid such timeouts.
-
-Note that the 50µS, 15mSec and 500mSec limits are guidelines
-and not hard constraints -- that is if you break them (slightly) then your code
-may (usually) work, but you can get very difficult to diagnose and intermittent
-failures. Also running ISRs from Flash may work until there is a collision with
-SPIFFS I/O which will then a cause CPU exception.
-
-Also note that the SDK API function `system_os_post()`, and the `task_post_*()`
-macros which generate this can be safely called from an ISR.
-
-The Lua runtime is NOT reentrant, and hence any code which calls any Lua API
-must run within a task context. Any such task is what we call a _Lua-Land Task_
-(or **LLT**). _ISRs must not access the Lua API or Lua resources._ LLTs can be
-executed as SDK API callbacks or OS tasks. They can also, of course, call the
-Lua execution system to execute Lua code (e.g. `luaL_dofile()` and related
-calls).
-
-Also since the application has no control over the relative time ordering of
-tasks and SDK API callbacks, LLTs can't make any assumptions about whether a
+The application has no control over the relative time ordering of
+tasks and API callbacks, and no assumptions can be made about whether a
task and any posted successors will run consecutively.
-This API is designed to complement the Lua library model, so that a library can
-declare one or more task handlers and that both ISPs and LLTs can then post a
-message for delivery to a task handler. Each task handler has a unique message
-associated with it, and may bind a single uint32 parameter. How this parameter
-is interpreted is left to the task poster and task handler to coordinate.
+\*) Non-preemptive in the sense that other NodeMCU tasks will not preempt it.
+The RTOS may still preempt the task to run ISRs or other threads.
-The interface is exposed through `#include "task/task.h"` and involves two API
-calls. Any task handlers are declared, typically in the module_init function by
-assigning `task_get_id(some_task_callback)` to a (typically globally) accessible
-handle variable, say `XXX_callback_handle`. This can then be used in an ISR or
-normal LLT to execute a `task_post_YYY(XXX_callback_handle,param)` where YYY is
-one of `low`, `medium`, `high`. The callback will then be executed when the SDK
-delivers the task.
+#### Task registration
+Each module which wishes to receive events and process those within the
+context of the Lua thread need to register their callback. This is done
+by calling `task_get_id(module_callback_fn)`. A non-zero return value
+indicates successful registration, and the returned handle can be used
+to post events to this task. The task registration is typically done in
+the module\_init function.
-_Note_: `task_post_YYY` can fail with a false return if the task Q is full.
+#### Task posting
+To signal a task, a message is posted to it using
+`task_post(prio, handle, param)` or the helper macros `task_post_low()/task_post_medium()/task_post_high()`. Each message carries a single parameter value
+of type `intptr_t` and may be used to carry either pointers or raw values.
+Each task defines its own schema depending on its needs.
+
+Note that the message queues can theoretically fill up, and this is reported
+by a `false` return value from the `task_post()` call. In this case no
+message was posted. In most cases nothing much can be done, and the best
+approach may be to simply ignore the failure and drop the data. Be careful
+not to accidentally leak memory in this circumstance however! A good
+habit would be to do something like this:
+```
+ char *buf = malloc (len);
+ // do something with buf...
+ if (!task_post_medium(handle, buf))
+ free (buf);
+```
+
+The `task_post*()` function and macros can be safely called from any ISR or
+thread.
+
+#### Task processing and priorities
+The Lua runtime executes in a single thread, and at its root level runs
+the task message processing loop. Task messages arrive on three queues,
+one for each priority level. The queues are services in order of their
+priority, so while a higher-priority queue contains messages, no
+lower-priority messages will be delivered. It is thus quite possible to
+jam up the Lua runtime by continually posting high-priority messages.
+Don't do that.
+
+Unless there are particular reasons not to, messages should be posted
+with medium priority. This is the friendly, cooperative level. If something
+is time sensitive (e.g. audio buffer running low), high priority may be
+warranted. The low priority level is intended for background processing
+during otherwise idle time.