Revert "[luci_context] Move writing of LUCI_CONTEXT files to the engine core."

This reverts commit 6126bc2f1092567bb3b0d96f0381330ba01ead75.

Reason for revert: Some steps are wiping out temp_dir which also accidentally deleted LUCI_CONTEXT file. See: https://crbug.com/1135137

Original change's description:
> [luci_context] Move writing of LUCI_CONTEXT files to the engine core.
>
> This removes an odd file-write from 'userspace' recipe code, but also
> enables compuation of 'timeout' for the step much closer to the actual
> step execution. Specifically, this avoids having 'timeout' also cover
> the time that we block for resource contention when running multiple
> greenlets in the recipe.
>
> R=​vadimsh, yiwzhang
>
> Bug: 1127089
> Change-Id: Iff59e7698e9556737a6144508c00cb3342811641
> Reviewed-on: https://chromium-review.googlesource.com/c/infra/luci/recipes-py/+/2441885
> Reviewed-by: Yiwei Zhang <yiwzhang@google.com>
> Commit-Queue: Robbie Iannucci <iannucci@chromium.org>

TBR=iannucci@chromium.org,vadimsh@chromium.org,yiwzhang@google.com,infra-scoped@luci-project-accounts.iam.gserviceaccount.com

# Not skipping CQ checks because original CL landed > 1 day ago.

Bug: 1127089
Change-Id: I9c3dec72e64238397d88293d24508b44c057491c
Reviewed-on: https://chromium-review.googlesource.com/c/infra/luci/recipes-py/+/2451447
Reviewed-by: Yiwei Zhang <yiwzhang@google.com>
Commit-Queue: Yiwei Zhang <yiwzhang@google.com>
diff --git a/README.recipes.md b/README.recipes.md
index f5ed9e7..eddae46 100644
--- a/README.recipes.md
+++ b/README.recipes.md
@@ -165,7 +165,7 @@
 
 [DEPS](/recipe_modules/archive/__init__.py#5): [json](#recipe_modules-json), [path](#recipe_modules-path), [platform](#recipe_modules-platform), [python](#recipe_modules-python), [step](#recipe_modules-step)
 
-#### **class [ArchiveApi](/recipe_modules/archive/api.py#8)([RecipeApi](/recipe_engine/recipe_api.py#856)):**
+#### **class [ArchiveApi](/recipe_modules/archive/api.py#8)([RecipeApi](/recipe_engine/recipe_api.py#877)):**
 
 Provides steps to manipulate archive files (tar, zip, etc.).
 
@@ -225,7 +225,7 @@
   Package object.
 ### *recipe_modules* / [assertions](/recipe_modules/assertions)
 
-#### **class [AssertionsApi](/recipe_modules/assertions/api.py#56)([RecipeApi](/recipe_engine/recipe_api.py#856)):**
+#### **class [AssertionsApi](/recipe_modules/assertions/api.py#56)([RecipeApi](/recipe_engine/recipe_api.py#877)):**
 
 Provides access to the assertion methods of the python unittest module.
 
@@ -283,7 +283,7 @@
 `build_pb2.Build` and returns a link title.
 If it returns `None`, the link is not reported. Default link title is build id.
 
-#### **class [BuildbucketApi](/recipe_modules/buildbucket/api.py#29)([RecipeApi](/recipe_engine/recipe_api.py#856)):**
+#### **class [BuildbucketApi](/recipe_modules/buildbucket/api.py#29)([RecipeApi](/recipe_engine/recipe_api.py#877)):**
 
 A module for interacting with buildbucket.
 
@@ -667,7 +667,7 @@
 
 API for interacting with cas client.
 
-#### **class [CasApi](/recipe_modules/cas/api.py#13)([RecipeApi](/recipe_engine/recipe_api.py#856)):**
+#### **class [CasApi](/recipe_modules/cas/api.py#13)([RecipeApi](/recipe_engine/recipe_api.py#877)):**
 
 A module for interacting with cas client.
 
@@ -709,7 +709,7 @@
 Depends on 'cipd' binary available in PATH:
 https://godoc.org/go.chromium.org/luci/cipd/client/cmd/cipd
 
-#### **class [CIPDApi](/recipe_modules/cipd/api.py#202)([RecipeApi](/recipe_engine/recipe_api.py#856)):**
+#### **class [CIPDApi](/recipe_modules/cipd/api.py#202)([RecipeApi](/recipe_engine/recipe_api.py#877)):**
 
 CIPDApi provides basic support for CIPD.
 
@@ -977,7 +977,7 @@
 Returns the CIPDApi.Pin instance.
 ### *recipe_modules* / [commit\_position](/recipe_modules/commit_position)
 
-#### **class [CommitPositionApi](/recipe_modules/commit_position/api.py#10)([RecipeApi](/recipe_engine/recipe_api.py#856)):**
+#### **class [CommitPositionApi](/recipe_modules/commit_position/api.py#10)([RecipeApi](/recipe_engine/recipe_api.py#877)):**
 
 Recipe module providing commit position parsing and formatting.
 
@@ -1017,7 +1017,7 @@
   api.step("cat subdir/foo", ['cat', './foo'])
 ```
 
-#### **class [ContextApi](/recipe_modules/context/api.py#78)([RecipeApi](/recipe_engine/recipe_api.py#856)):**
+#### **class [ContextApi](/recipe_modules/context/api.py#78)([RecipeApi](/recipe_engine/recipe_api.py#877)):**
 
 &emsp; **@contextmanager**<br>&mdash; **def [\_\_call\_\_](/recipe_modules/context/api.py#108)(self, cwd=None, env_prefixes=None, env_suffixes=None, env=None, infra_steps=None, luciexe=None, realm=None):**
 
@@ -1067,7 +1067,7 @@
 
 Look at the examples in "examples/" for examples of context module usage.
 
-&emsp; **@property**<br>&mdash; **def [cwd](/recipe_modules/context/api.py#234)(self):**
+&emsp; **@property**<br>&mdash; **def [cwd](/recipe_modules/context/api.py#242)(self):**
 
 Returns the current working directory that steps will run in.
 
@@ -1075,7 +1075,7 @@
 equivalent to api.path['start_dir'], though only occurs if no cwd has been
 set (e.g. in the outermost context of RunSteps).
 
-&emsp; **@property**<br>&mdash; **def [env](/recipe_modules/context/api.py#244)(self):**
+&emsp; **@property**<br>&mdash; **def [env](/recipe_modules/context/api.py#252)(self):**
 
 Returns modifications to the environment.
 
@@ -1086,7 +1086,7 @@
 **Returns (dict)** - The env-key -> value mapping of current environment
   modifications.
 
-&emsp; **@property**<br>&mdash; **def [env\_prefixes](/recipe_modules/context/api.py#259)(self):**
+&emsp; **@property**<br>&mdash; **def [env\_prefixes](/recipe_modules/context/api.py#267)(self):**
 
 Returns Path prefix modifications to the environment.
 
@@ -1096,7 +1096,7 @@
 **Returns (dict)** - The env-key -> value(Path) mapping of current
 environment prefix modifications.
 
-&emsp; **@property**<br>&mdash; **def [env\_suffixes](/recipe_modules/context/api.py#273)(self):**
+&emsp; **@property**<br>&mdash; **def [env\_suffixes](/recipe_modules/context/api.py#281)(self):**
 
 Returns Path suffix modifications to the environment.
 
@@ -1106,7 +1106,7 @@
 **Returns (dict)** - The env-key -> value(Path) mapping of current
 environment suffix modifications.
 
-&emsp; **@property**<br>&mdash; **def [infra\_step](/recipe_modules/context/api.py#287)(self):**
+&emsp; **@property**<br>&mdash; **def [infra\_step](/recipe_modules/context/api.py#295)(self):**
 
 Returns the current value of the infra_step setting.
 
@@ -1114,19 +1114,19 @@
 
 &mdash; **def [initialize](/recipe_modules/context/api.py#88)(self):**
 
-&emsp; **@property**<br>&mdash; **def [luci\_context](/recipe_modules/context/api.py#295)(self):**
+&emsp; **@property**<br>&mdash; **def [luci\_context](/recipe_modules/context/api.py#303)(self):**
 
 Returns the currently tracked LUCI_CONTEXT sections as a dict of proto
 messages.
 
 Only contains `luciexe`, `realm`, and `deadline`.
 
-&emsp; **@property**<br>&mdash; **def [luciexe](/recipe_modules/context/api.py#307)(self):**
+&emsp; **@property**<br>&mdash; **def [luciexe](/recipe_modules/context/api.py#315)(self):**
 
 Returns the current value (sections_pb2.LUCIExe) of luciexe section in
 the current LUCI_CONTEXT. Returns None if luciexe is not defined.
 
-&emsp; **@property**<br>&mdash; **def [realm](/recipe_modules/context/api.py#317)(self):**
+&emsp; **@property**<br>&mdash; **def [realm](/recipe_modules/context/api.py#325)(self):**
 
 Returns the LUCI realm of the current context.
 
@@ -1136,7 +1136,7 @@
 
 [DEPS](/recipe_modules/cq/__init__.py#7): [buildbucket](#recipe_modules-buildbucket), [properties](#recipe_modules-properties), [step](#recipe_modules-step)
 
-#### **class [CQApi](/recipe_modules/cq/api.py#14)([RecipeApi](/recipe_engine/recipe_api.py#856)):**
+#### **class [CQApi](/recipe_modules/cq/api.py#14)([RecipeApi](/recipe_engine/recipe_api.py#877)):**
 
 This module provides recipe API of LUCI CQ, aka pre-commit testing system.
 
@@ -1266,7 +1266,7 @@
 
 File manipulation (read/write/delete/glob) methods.
 
-#### **class [FileApi](/recipe_modules/file/api.py#83)([RecipeApi](/recipe_engine/recipe_api.py#856)):**
+#### **class [FileApi](/recipe_modules/file/api.py#83)([RecipeApi](/recipe_engine/recipe_api.py#877)):**
 
 &mdash; **def [compute\_hash](/recipe_modules/file/api.py#182)(self, name, paths, base_path, test_data=''):**
 
@@ -1656,7 +1656,7 @@
 
 Implements in-recipe concurrency via green threads.
 
-#### **class [FuturesApi](/recipe_modules/futures/api.py#42)([RecipeApi](/recipe_engine/recipe_api.py#856)):**
+#### **class [FuturesApi](/recipe_modules/futures/api.py#42)([RecipeApi](/recipe_engine/recipe_api.py#877)):**
 
 Provides access to the Recipe concurrency primitives.
 
@@ -1805,7 +1805,7 @@
 another repo. It is not recommended to use this, and it will be removed in the
 near future.
 
-#### **class [GeneratorScriptApi](/recipe_modules/generator_script/api.py#16)([RecipeApi](/recipe_engine/recipe_api.py#856)):**
+#### **class [GeneratorScriptApi](/recipe_modules/generator_script/api.py#16)([RecipeApi](/recipe_engine/recipe_api.py#877)):**
 
 &mdash; **def [\_\_call\_\_](/recipe_modules/generator_script/api.py#44)(self, path_to_script, \*args):**
 
@@ -1846,7 +1846,7 @@
 
 [DEPS](/recipe_modules/isolated/__init__.py#1): [cipd](#recipe_modules-cipd), [context](#recipe_modules-context), [json](#recipe_modules-json), [path](#recipe_modules-path), [properties](#recipe_modules-properties), [raw\_io](#recipe_modules-raw_io), [runtime](#recipe_modules-runtime), [step](#recipe_modules-step)
 
-#### **class [IsolatedApi](/recipe_modules/isolated/api.py#15)([RecipeApi](/recipe_engine/recipe_api.py#856)):**
+#### **class [IsolatedApi](/recipe_modules/isolated/api.py#15)([RecipeApi](/recipe_engine/recipe_api.py#877)):**
 
 API for interacting with isolated.
 
@@ -1905,7 +1905,7 @@
 
 Methods for producing and consuming JSON.
 
-#### **class [JsonApi](/recipe_modules/json/api.py#95)([RecipeApi](/recipe_engine/recipe_api.py#856)):**
+#### **class [JsonApi](/recipe_modules/json/api.py#95)([RecipeApi](/recipe_engine/recipe_api.py#877)):**
 
 &emsp; **@staticmethod**<br>&mdash; **def [dumps](/recipe_modules/json/api.py#96)(\*args, \*\*kwargs):**
 
@@ -1946,7 +1946,7 @@
 
 [DEPS](/recipe_modules/led/__init__.py#5): [cipd](#recipe_modules-cipd), [context](#recipe_modules-context), [json](#recipe_modules-json), [path](#recipe_modules-path), [proto](#recipe_modules-proto), [step](#recipe_modules-step)
 
-#### **class [LedApi](/recipe_modules/led/api.py#15)([RecipeApi](/recipe_engine/recipe_api.py#856)):**
+#### **class [LedApi](/recipe_modules/led/api.py#15)([RecipeApi](/recipe_engine/recipe_api.py#877)):**
 
 Interface to the led tool.
 
@@ -2014,7 +2014,7 @@
 build (using the Merge Step feature from luciexe protocol). This is the
 replacement for allow_subannotation feature in the legacy annotate mode.
 
-#### **class [LegacyAnnotationApi](/recipe_modules/legacy_annotation/api.py#24)([RecipeApiPlain](/recipe_engine/recipe_api.py#708)):**
+#### **class [LegacyAnnotationApi](/recipe_modules/legacy_annotation/api.py#24)([RecipeApiPlain](/recipe_engine/recipe_api.py#729)):**
 
 &mdash; **def [\_\_call\_\_](/recipe_modules/legacy_annotation/api.py#28)(self, name, cmd, timeout=None, step_test_data=None, cost=_ResourceCost()):**
 
@@ -2029,7 +2029,7 @@
 
 API for specifying Milo behavior.
 
-#### **class [MiloApi](/recipe_modules/milo/api.py#17)([RecipeApi](/recipe_engine/recipe_api.py#856)):**
+#### **class [MiloApi](/recipe_modules/milo/api.py#17)([RecipeApi](/recipe_engine/recipe_api.py#877)):**
 
 A module for interacting with Milo.
 
@@ -2080,7 +2080,7 @@
 `depot_tools/infra_paths` module). Refer to those modules for additional
 documentation.
 
-#### **class [PathApi](/recipe_modules/path/api.py#206)([RecipeApi](/recipe_engine/recipe_api.py#856)):**
+#### **class [PathApi](/recipe_modules/path/api.py#206)([RecipeApi](/recipe_engine/recipe_api.py#877)):**
 
 &mdash; **def [\_\_getitem\_\_](/recipe_modules/path/api.py#438)(self, name):**
 
@@ -2246,7 +2246,7 @@
 
 Mockable system platform identity functions.
 
-#### **class [PlatformApi](/recipe_modules/platform/api.py#24)([RecipeApi](/recipe_engine/recipe_api.py#856)):**
+#### **class [PlatformApi](/recipe_modules/platform/api.py#24)([RecipeApi](/recipe_engine/recipe_api.py#877)):**
 
 Provides host-platform-detection properties.
 
@@ -2331,7 +2331,7 @@
 intentionally no API to write property values (lest they become a kind of
 random-access global variable).
 
-#### **class [PropertiesApi](/recipe_modules/properties/api.py#28)([RecipeApiPlain](/recipe_engine/recipe_api.py#708), collections.Mapping):**
+#### **class [PropertiesApi](/recipe_modules/properties/api.py#28)([RecipeApiPlain](/recipe_engine/recipe_api.py#729), collections.Mapping):**
 
 PropertiesApi implements all the standard Mapping functions, so you
 can use it like a read-only dict.
@@ -2358,7 +2358,7 @@
 Methods for producing and consuming protobuf data to/from steps and the
 filesystem.
 
-#### **class [ProtoApi](/recipe_modules/proto/api.py#75)([RecipeApi](/recipe_engine/recipe_api.py#856)):**
+#### **class [ProtoApi](/recipe_modules/proto/api.py#75)([RecipeApi](/recipe_engine/recipe_api.py#877)):**
 
 &emsp; **@staticmethod**<br>&mdash; **def [decode](/recipe_modules/proto/api.py#151)(data, msg_class, codec, \*\*decoding_kwargs):**
 
@@ -2435,7 +2435,7 @@
 correctly for bots (e.g. ensuring that python is working on Windows, passing the
 unbuffered flag, etc.)
 
-#### **class [PythonApi](/recipe_modules/python/api.py#17)([RecipeApi](/recipe_engine/recipe_api.py#856)):**
+#### **class [PythonApi](/recipe_modules/python/api.py#17)([RecipeApi](/recipe_engine/recipe_api.py#877)):**
 
 &mdash; **def [\_\_call\_\_](/recipe_modules/python/api.py#18)(self, name, script, args=None, unbuffered=True, venv=None, \*\*kwargs):**
 
@@ -2519,7 +2519,7 @@
       api.random.shuffle(my_list)
       # my_list is now random!
 
-#### **class [RandomApi](/recipe_modules/random/api.py#31)([RecipeApi](/recipe_engine/recipe_api.py#856)):**
+#### **class [RandomApi](/recipe_modules/random/api.py#31)([RecipeApi](/recipe_engine/recipe_api.py#877)):**
 
 &mdash; **def [\_\_getattr\_\_](/recipe_modules/random/api.py#38)(self, name):**
 
@@ -2530,7 +2530,7 @@
 
 Provides objects for reading and writing raw data to and from steps.
 
-#### **class [RawIOApi](/recipe_modules/raw_io/api.py#297)([RecipeApi](/recipe_engine/recipe_api.py#856)):**
+#### **class [RawIOApi](/recipe_modules/raw_io/api.py#297)([RecipeApi](/recipe_engine/recipe_api.py#877)):**
 
 &emsp; **@[returns\_placeholder](/recipe_engine/util.py#151)**<br>&emsp; **@staticmethod**<br>&mdash; **def [input](/recipe_modules/raw_io/api.py#298)(data, suffix='', name=None):**
 
@@ -2625,7 +2625,7 @@
 Requires `rdb` command in `$PATH`:
 https://godoc.org/go.chromium.org/luci/resultdb/cmd/rdb
 
-#### **class [ResultDBAPI](/recipe_modules/resultdb/api.py#19)([RecipeApi](/recipe_engine/recipe_api.py#856)):**
+#### **class [ResultDBAPI](/recipe_modules/resultdb/api.py#19)([RecipeApi](/recipe_engine/recipe_api.py#877)):**
 
 A module for interacting with ResultDB.
 
@@ -2747,7 +2747,7 @@
     may be repeated.
 ### *recipe_modules* / [runtime](/recipe_modules/runtime)
 
-#### **class [RuntimeApi](/recipe_modules/runtime/api.py#8)([RecipeApi](/recipe_engine/recipe_api.py#856)):**
+#### **class [RuntimeApi](/recipe_modules/runtime/api.py#8)([RecipeApi](/recipe_engine/recipe_api.py#877)):**
 
 This module assists in experimenting with production recipes.
 
@@ -2776,7 +2776,7 @@
 RPCExplorer available at
   https://luci-scheduler.appspot.com/rpcexplorer/services/scheduler.Scheduler
 
-#### **class [SchedulerApi](/recipe_modules/scheduler/api.py#26)([RecipeApi](/recipe_engine/recipe_api.py#856)):**
+#### **class [SchedulerApi](/recipe_modules/scheduler/api.py#26)([RecipeApi](/recipe_engine/recipe_api.py#877)):**
 
 A module for interacting with LUCI Scheduler service.
 
@@ -2832,7 +2832,7 @@
 
 Depends on luci-auth to be in PATH.
 
-#### **class [ServiceAccountApi](/recipe_modules/service_account/api.py#16)([RecipeApi](/recipe_engine/recipe_api.py#856)):**
+#### **class [ServiceAccountApi](/recipe_modules/service_account/api.py#16)([RecipeApi](/recipe_engine/recipe_api.py#877)):**
 
 &mdash; **def [default](/recipe_modules/service_account/api.py#57)(self):**
 
@@ -2858,7 +2858,7 @@
 Step is the primary API for running steps (external programs, scripts,
 etc.).
 
-#### **class [StepApi](/recipe_modules/step/api.py#26)([RecipeApiPlain](/recipe_engine/recipe_api.py#708)):**
+#### **class [StepApi](/recipe_modules/step/api.py#26)([RecipeApiPlain](/recipe_engine/recipe_api.py#729)):**
 
 &emsp; **@property**<br>&mdash; **def [InfraFailure](/recipe_modules/step/api.py#138)(self):**
 
@@ -3174,7 +3174,7 @@
 
 [DEPS](/recipe_modules/swarming/__init__.py#8): [buildbucket](#recipe_modules-buildbucket), [cas](#recipe_modules-cas), [cipd](#recipe_modules-cipd), [context](#recipe_modules-context), [isolated](#recipe_modules-isolated), [json](#recipe_modules-json), [path](#recipe_modules-path), [properties](#recipe_modules-properties), [raw\_io](#recipe_modules-raw_io), [runtime](#recipe_modules-runtime), [step](#recipe_modules-step)
 
-#### **class [SwarmingApi](/recipe_modules/swarming/api.py#1209)([RecipeApi](/recipe_engine/recipe_api.py#856)):**
+#### **class [SwarmingApi](/recipe_modules/swarming/api.py#1209)([RecipeApi](/recipe_engine/recipe_api.py#877)):**
 
 API for interacting with swarming.
 
@@ -3276,7 +3276,7 @@
 
 Allows mockable access to the current time.
 
-#### **class [TimeApi](/recipe_modules/time/api.py#12)([RecipeApi](/recipe_engine/recipe_api.py#856)):**
+#### **class [TimeApi](/recipe_modules/time/api.py#12)([RecipeApi](/recipe_engine/recipe_api.py#877)):**
 
 &mdash; **def [ms\_since\_epoch](/recipe_modules/time/api.py#49)(self):**
 
@@ -3304,7 +3304,7 @@
 
 API for Tricium analyzers to use.
 
-#### **class [TriciumApi](/recipe_modules/tricium/api.py#15)([RecipeApi](/recipe_engine/recipe_api.py#856)):**
+#### **class [TriciumApi](/recipe_modules/tricium/api.py#15)([RecipeApi](/recipe_engine/recipe_api.py#877)):**
 
 TriciumApi provides basic support for Tricium.
 
@@ -3326,7 +3326,7 @@
 
 Methods for interacting with HTTP(s) URLs.
 
-#### **class [UrlApi](/recipe_modules/url/api.py#15)([RecipeApi](/recipe_engine/recipe_api.py#856)):**
+#### **class [UrlApi](/recipe_modules/url/api.py#15)([RecipeApi](/recipe_engine/recipe_api.py#877)):**
 
 &mdash; **def [get\_file](/recipe_modules/url/api.py#131)(self, url, path, step_name=None, headers=None, transient_retry=True, strip_prefix=None, timeout=None):**
 
@@ -3431,7 +3431,7 @@
 
 Allows test-repeatable access to a random UUID.
 
-#### **class [UuidApi](/recipe_modules/uuid/api.py#11)([RecipeApi](/recipe_engine/recipe_api.py#856)):**
+#### **class [UuidApi](/recipe_modules/uuid/api.py#11)([RecipeApi](/recipe_engine/recipe_api.py#877)):**
 
 &mdash; **def [random](/recipe_modules/uuid/api.py#20)(self):**
 
@@ -3440,7 +3440,7 @@
 
 Thin API for parsing semver strings into comparable object.
 
-#### **class [VersionApi](/recipe_modules/version/api.py#12)([RecipeApi](/recipe_engine/recipe_api.py#856)):**
+#### **class [VersionApi](/recipe_modules/version/api.py#12)([RecipeApi](/recipe_engine/recipe_api.py#877)):**
 
 &emsp; **@staticmethod**<br>&mdash; **def [parse](/recipe_modules/version/api.py#14)(version):**
 
@@ -3456,7 +3456,7 @@
 
 Allows recipe modules to issue warnings in simulation test.
 
-#### **class [WarningApi](/recipe_modules/warning/api.py#12)([RecipeApiPlain](/recipe_engine/recipe_api.py#708)):**
+#### **class [WarningApi](/recipe_modules/warning/api.py#12)([RecipeApiPlain](/recipe_engine/recipe_api.py#729)):**
 
 &emsp; **@recipe_api.escape_all_warnings**<br>&mdash; **def [issue](/recipe_modules/warning/api.py#15)(self, name):**
 
diff --git a/recipe_engine/internal/engine.py b/recipe_engine/internal/engine.py
index a0c4417..3f3c01c 100644
--- a/recipe_engine/internal/engine.py
+++ b/recipe_engine/internal/engine.py
@@ -15,7 +15,6 @@
 import attr
 import gevent
 import gevent.local
-import six
 
 from google.protobuf import json_format as jsonpb
 from pympler import summary, tracker
@@ -29,7 +28,6 @@
 from ..step_data import StepData, ExecutionResult
 from ..types import StepPresentation, thaw
 from ..types import PerGreenletState, PerGreentletStateRegistry
-from ..third_party import luci_context
 
 from .engine_env import merge_envs
 from .exceptions import RecipeUsageError, CrashEngine
@@ -117,8 +115,8 @@
   """
 
   def __init__(self, recipe_deps, step_runner, stream_engine, warning_recorder,
-               properties, environ, start_dir, initial_luci_context,
-               num_logical_cores, memory_mb):
+               properties, environ, start_dir, luci_context, num_logical_cores,
+               memory_mb):
     """See run_steps() for parameter meanings."""
     self._recipe_deps = recipe_deps
     self._step_runner = step_runner
@@ -131,7 +129,7 @@
         recipe_api.ConcurrencyClient(
             stream_engine.supports_concurrency,
             self.spawn_greenlet),
-        recipe_api.LUCIContextClient(initial_luci_context),
+        recipe_api.LUCIContextClient(luci_context),
         recipe_api.PathsClient(start_dir),
         recipe_api.PropertiesClient(properties),
         recipe_api.StepClient(self),
@@ -474,7 +472,7 @@
 
   @classmethod
   def run_steps(cls, recipe_deps, properties, stream_engine, step_runner,
-                warning_recorder, environ, cwd, initial_luci_context,
+                warning_recorder, environ, cwd, luci_context,
                 num_logical_cores, memory_mb,
                 emit_initial_properties=False, test_data=None,
                 skip_setup_build=False):
@@ -493,8 +491,8 @@
       * environ: The mapping object representing the environment in which
         recipe runs. Generally obtained via `os.environ`.
       * cwd (str): The current working directory to run the recipe.
-      * initial_luci_context (Dict[str, Dict]): The content of LUCI_CONTEXT to
-        pass to the recipe.
+      * luci_context (Dict[str, Dict]): The content of LUCI_CONTEXT to pass
+        to the recipe.
       * num_logical_cores (int): The number of logical CPU cores to assume the
         machine has.
       * memory_mb (int): The amount of memory to assume the machine has, in MiB.
@@ -526,8 +524,7 @@
 
       engine = cls(
           recipe_deps, step_runner, stream_engine, warning_recorder,
-          properties, environ, cwd, initial_luci_context, num_logical_cores,
-          memory_mb)
+          properties, environ, cwd, luci_context, num_logical_cores, memory_mb)
       api = recipe_obj.mk_api(engine, test_data)
       engine.initialize_path_client_HACK(api)
     except (RecipeUsageError, ImportError, AssertionError) as ex:
@@ -768,25 +765,16 @@
       pathsep)
   env.update(step_stream.env_vars)
 
-  if step_config.luci_context:
-    debug.write_line('writing LUCI_CONTEXT file')
-    lctx_file = step_runner.write_luci_context({
-      key: jsonpb.MessageToDict(pb_val) if pb_val is not None else None
-      for key, pb_val in six.iteritems(step_config.luci_context)
-    })
-    debug.write_line('  done: %r' % (lctx_file,))
-    env[luci_context.ENV_KEY] = lctx_file
-
   debug.write_line('checking cwd: %r' % (step_config.cwd,))
   cwd = step_config.cwd or start_dir
   if not step_runner.isabs(name_tokens, cwd):
-    debug.write_line('  not absolute: %r' % (cwd,))
+    debug.write_line('  not absolute: %r' % (cwd))
     return None
   if not step_runner.isdir(name_tokens, cwd):
-    debug.write_line('  not a directory: %r' % (cwd,))
+    debug.write_line('  not a directory: %r' % (cwd))
     return None
   if not step_runner.access(name_tokens, cwd, os.R_OK):
-    debug.write_line('  no read perms: %r' % (cwd,))
+    debug.write_line('  no read perms: %r' % (cwd))
     return None
 
   path = env.get('PATH', '').split(pathsep)
diff --git a/recipe_engine/internal/step_runner/__init__.py b/recipe_engine/internal/step_runner/__init__.py
index 862e844..7cfa151 100644
--- a/recipe_engine/internal/step_runner/__init__.py
+++ b/recipe_engine/internal/step_runner/__init__.py
@@ -135,15 +135,6 @@
     """
     return cmd0
 
-  def write_luci_context(self, section_values):
-    """Writes a mapping of str->dict to disk (as a temp file), returning that
-    path.
-
-    `section_values` represents the 'diff' against LUCI_CONTEXT for the
-    recipe engine process. The standard LUCI_CONTEXT merge rules should apply.
-    """
-    raise NotImplementedError()
-
   def run(self, name_tokens, debug_log, step):
     """Runs the step defined by step_config.
 
diff --git a/recipe_engine/internal/step_runner/sim.py b/recipe_engine/internal/step_runner/sim.py
index af42b49..3f3564a 100644
--- a/recipe_engine/internal/step_runner/sim.py
+++ b/recipe_engine/internal/step_runner/sim.py
@@ -104,10 +104,6 @@
           self._used_steps[dot_name], handle_name)
     return self._used_placeholders[key]
 
-  def write_luci_context(self, section_pb_values):
-    # We ignore this environment variable anyway.
-    return ""
-
   def run(self, name_tokens, debug_log, step):
     del debug_log  # unused
 
diff --git a/recipe_engine/internal/step_runner/subproc.py b/recipe_engine/internal/step_runner/subproc.py
index be86d0c..1f085be 100644
--- a/recipe_engine/internal/step_runner/subproc.py
+++ b/recipe_engine/internal/step_runner/subproc.py
@@ -6,14 +6,12 @@
 import signal
 import sys
 
-from gevent import subprocess
 import attr
 import gevent
+from gevent import subprocess
 
 from ...step_data import ExecutionResult
 
-from ...third_party import luci_context
-
 from . import StepRunner
 
 _MSWINDOWS = sys.platform == 'win32'
@@ -155,10 +153,6 @@
 
     return None
 
-  def write_luci_context(self, section_values):
-    with luci_context.stage(_leak=True, **section_values) as file_path:
-      return file_path
-
   def run(self, name_tokens, debug_log, step):
     proc, gid, pipes = self._mk_proc(step, debug_log)
 
@@ -307,8 +301,8 @@
           'finished waiting for process, retcode %r' % ret.retcode)
 
       # TODO(iannucci): Make leaking subprocesses explicit (e.g. goma compiler
-      # daemon). Better, change deamons to be owned by a gevent Greenlet (so
-      # that we don't need to leak processes ever).
+      # daemon). Better, change deamons to be owned by a gevent Greenlet (so that
+      # we don't need to leak processes ever).
       #
       # _kill(proc, gid)  # In case of leaked subprocesses or timeout.
       if ret.retcode is None:
diff --git a/recipe_engine/recipe_api.py b/recipe_engine/recipe_api.py
index f249824..d2b0681 100644
--- a/recipe_engine/recipe_api.py
+++ b/recipe_engine/recipe_api.py
@@ -20,6 +20,7 @@
 
 from six import iteritems
 from google.protobuf import message
+from google.protobuf import json_format as jsonpb
 
 from .config_types import Path
 from .internal import engine_step
@@ -108,6 +109,26 @@
   initial_context = attr.ib(validator=attr_dict_type(str, (dict, FrozenDict)),
                             factory=dict, converter=freeze)
 
+  def new_context(self, **section_pb_values):
+    """Creates a new LUCI_CONTEXT file with the provided section values, all
+    unmentioned sections in the current context will be copied over. The
+    environment variable will NOT not be switched to the newly created context.
+
+    Args:
+      * section_pb_values (Dict[str, message.Message]) - A mapping of
+        section_key to the new message value for that section. If the value
+        is None, the corresponding section will be removed from the context.
+
+    Returns the path (str) to the newly created LUCI_CONTEXT file. Returns None
+    if section_pb_values is empty (i.e. No change to current context).
+    """
+    section_values = {
+      key: jsonpb.MessageToDict(pb_val) if pb_val is not None else None
+      for key, pb_val in iteritems(section_pb_values)
+    }
+    with luci_context.stage(_leak=True, **section_values) as file_path:
+      return file_path
+
 
 class PathsClient(object):
   """A recipe engine client which exposes all known base paths.
diff --git a/recipe_modules/context/api.py b/recipe_modules/context/api.py
index 5e7be93..2715f69 100644
--- a/recipe_modules/context/api.py
+++ b/recipe_modules/context/api.py
@@ -219,6 +219,14 @@
             sections_pb2.Realm(name=realm) if realm else None)
       if section_pb_values:
         _add_to_context('luci_context', section_pb_values, _override)
+        env = {} if env is None else dict(env)
+        if self._test_data.enabled:
+          self._test_counter += 1
+          env[self._lucictx_client.ENV_KEY] = (
+            '/path/to/lucictx_%d.json' % self._test_counter)
+        else: # pragma: no cover
+          env[self._lucictx_client.ENV_KEY] = (
+            self._lucictx_client.new_context(**section_pb_values))
 
       _add_to_context('env_prefixes', env_prefixes, _as_env_prefixes)