wip
diff --git a/bin/dartdevc.dart b/bin/dartdevc.dart
index 6be7a96..2246a9c 100755
--- a/bin/dartdevc.dart
+++ b/bin/dartdevc.dart
@@ -40,39 +40,27 @@
 import 'package:dev_compiler/src/compiler/command.dart';
 
 main(List<String> args) async {
+  args = _preprocessArgs(args);
+
   var runner = new CommandRunner('dartdevc', 'Dart Development Compiler');
   runner.addCommand(new CompileCommand());
   try {
     await runner.run(args);
-  } on UsageException catch (e) {
-    // Incorrect usage, input file not found, etc.
-    print(e);
-    exit(64);
-  } on CompileErrorException catch (e) {
-    // Code has error(s) and failed to compile.
-    print(e);
-    exit(1);
-  } catch (e, s) {
-    // Anything else is likely a compiler bug.
-    //
-    // --unsafe-force-compile is a bit of a grey area, but it's nice not to
-    // crash while compiling
-    // (of course, output code may crash, if it had errors).
-    //
-    print("");
-    print("We're sorry, you've found a bug in our compiler.");
-    print("You can report this bug at:");
-    print("    https://github.com/dart-lang/dev_compiler/issues");
-    print("");
-    print("Please include the information below in your report, along with");
-    print("any other information that may help us track it down. Thanks!");
-    print("");
-    print("    dartdevc arguments: " + args.join(' '));
-    print("    dart --version: ${Platform.version}");
-    print("");
-    print("```");
-    print(e);
-    print(s);
-    print("```");
+  } catch(e, s) {
+    exit(handleError(e, s, args));
+  }
+}
+
+/// If the final arg is `@file_path` then read in all the lines of that file
+/// and add those as args.
+///
+/// Bazel actions that support workers must provide all their per-WorkRequest
+/// arguments in a file like this instead of as normal args.
+List<String> _preprocessArgs(List<String> args) {
+  if (args.last.startsWith('@')) {
+    return new List.from(args)
+      ..addAll(new File(args.last.substring(1)).readAsLinesSync());
+  } else {
+    return args;
   }
 }
diff --git a/lib/src/compiler/command.dart b/lib/src/compiler/command.dart
index 2b0eca1..686dddf 100644
--- a/lib/src/compiler/command.dart
+++ b/lib/src/compiler/command.dart
@@ -8,6 +8,7 @@
 import 'package:analyzer/src/generated/source.dart' show Source;
 import 'package:analyzer/src/summary/package_bundle_reader.dart'
     show InSummarySource;
+import 'package:bazel_worker/bazel_worker.dart';
 import 'compiler.dart'
     show BuildUnit, CompilerOptions, JSModuleFile, ModuleCompiler;
 import '../analyzer/context.dart' show AnalyzerOptions;
@@ -20,26 +21,40 @@
 
   CompileCommand() {
     argParser.addOption('out', abbr: 'o', help: 'Output file (required)');
+    argParser.addFlag('persistent_worker',
+        help: 'Run in a persistent Bazel worker (http://bazel.io/)\n',
+        defaultsTo: false,
+        hide: true);
     CompilerOptions.addArguments(argParser);
     AnalyzerOptions.addArguments(argParser);
   }
 
   @override
   void run() {
-    var compilerOptions = new CompilerOptions.fromArguments(argResults);
-    var compiler =
-        new ModuleCompiler(new AnalyzerOptions.fromArguments(argResults));
+    var analyzerOptions = new AnalyzerOptions.fromArguments(argResults);
+    if (argResults['persistent_worker']) {
+      new _CompilerWorker(analyzerOptions, this).run();
+    } else {
+      compile(
+          new ModuleCompiler(analyzerOptions),
+          new CompilerOptions.fromArguments(argResults),
+          argResults['out'],
+          argResults.rest);
+    }
+  }
 
-    var outPath = argResults['out'];
+  void compile(ModuleCompiler compiler, CompilerOptions compilerOptions,
+      String outPath, List<String> extraArgs,
+      {void forEachError(String error): print}) {
     if (outPath == null) {
       usageException('Please include the output file location. For example:\n'
           '    -o PATH/TO/OUTPUT_FILE.js');
     }
-    var unit = new BuildUnit(path.basenameWithoutExtension(outPath),
-        argResults.rest, _moduleForLibrary);
+    var unit = new BuildUnit(
+        path.basenameWithoutExtension(outPath), extraArgs, _moduleForLibrary);
 
     JSModuleFile module = compiler.compile(unit, compilerOptions);
-    module.errors.forEach(print);
+    module.errors.forEach(forEachError);
 
     if (!module.isValid) throw new CompileErrorException();
 
@@ -68,7 +83,81 @@
   }
 }
 
+/// Handles [error] in a uniform fashion. Returns the proper exit code and calls
+/// [messageHandler] with messages.
+int handleError(dynamic error, dynamic stackTrace, List<String> args,
+    {void messageHandler(Object message): print}) {
+  if (error is UsageException) {
+    // Incorrect usage, input file not found, etc.
+    messageHandler(error);
+    return 64;
+  } else if (error is CompileErrorException) {
+    // Code has error(s) and failed to compile.
+    messageHandler(error);
+    return 1;
+  } else {
+    // Anything else is likely a compiler bug.
+    //
+    // --unsafe-force-compile is a bit of a grey area, but it's nice not to
+    // crash while compiling
+    // (of course, output code may crash, if it had errors).
+    //
+    messageHandler("");
+    messageHandler("We're sorry, you've found a bug in our compiler.");
+    messageHandler("You can report this bug at:");
+    messageHandler("    https://github.com/dart-lang/dev_compiler/issues");
+    messageHandler("");
+    messageHandler(
+        "Please include the information below in your report, along with");
+    messageHandler(
+        "any other information that may help us track it down. Thanks!");
+    messageHandler("");
+    messageHandler("    dartdevc arguments: " + args.join(' '));
+    messageHandler("    dart --version: ${Platform.version}");
+    messageHandler("");
+    messageHandler("```");
+    messageHandler(error);
+    messageHandler(stackTrace);
+    messageHandler("```");
+    return 1;
+  }
+}
+
 /// Thrown when the input source code has errors.
 class CompileErrorException implements Exception {
   toString() => '\nPlease fix all errors before compiling (warnings are okay).';
 }
+
+/// Runs the compiler worker loop.
+class _CompilerWorker extends SyncWorkerLoop {
+  final AnalyzerOptions analyzerOptions;
+  final CompileCommand compileCommand;
+
+  _CompilerWorker(this.analyzerOptions, this.compileCommand) : super();
+
+  WorkResponse performRequest(WorkRequest request) {
+    var arguments = new List.from(request.arguments)
+      ..addAll(compileCommand.argResults.rest);
+    var argResults = compileCommand.argParser.parse(arguments);
+
+    var output = new StringBuffer();
+    try {
+      compileCommand.compile(
+          new ModuleCompiler(analyzerOptions),
+          new CompilerOptions.fromArguments(argResults),
+          argResults['out'],
+          argResults.rest,
+          forEachError: output.writeln);
+      return new WorkResponse()
+        ..exitCode = EXIT_CODE_OK
+        ..output = output.toString();
+    } catch (e, s) {
+      var response = new WorkResponse();
+      var output = new StringBuffer();
+      response.exitCode =
+          handleError(e, s, request.arguments, messageHandler: output.writeln);
+      response.output = output.toString();
+      return response;
+    }
+  }
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index e9e9c1d..07fc732 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -10,6 +10,7 @@
 dependencies:
   analyzer: ^0.27.3
   args: ^0.13.0
+  bazel_worker: ^0.1.0
   cli_util: ^0.0.1
   func: ^0.1.0
   html: ^0.12.0
diff --git a/test/all_tests.dart b/test/all_tests.dart
index a74ff9d..71d48ac 100644
--- a/test/all_tests.dart
+++ b/test/all_tests.dart
@@ -11,6 +11,7 @@
 import 'closure/closure_type_test.dart' as closure_type_test;
 import 'codegen_test.dart' as codegen_test;
 import 'js/builder_test.dart' as builder_test;
+import 'worker/worker_test.dart' as worker_test;
 
 void main() {
   group('codegen', () => codegen_test.main([]));
@@ -19,4 +20,5 @@
     closure_type_test.main();
   });
   group('js', builder_test.main);
+  group('worker', worker_test.main);
 }
diff --git a/test/command/hello.dart b/test/command/hello.dart
deleted file mode 100644
index fc503dc..0000000
--- a/test/command/hello.dart
+++ /dev/null
@@ -1,3 +0,0 @@
-// This is compiled by ./tool/command_test.sh
-import 'hello_lib.dart';
-main() => print(greeting);
diff --git a/test/command/hello.js b/test/command/hello.js
deleted file mode 100644
index f9d2dd4..0000000
--- a/test/command/hello.js
+++ /dev/null
@@ -1,19 +0,0 @@
-dart_library.library('hello', null, /* Imports */[
-  'dart_sdk',
-  'hello_lib'
-], function(exports, dart_sdk, hello_lib) {
-  'use strict';
-  const core = dart_sdk.core;
-  const dart = dart_sdk.dart;
-  const dartx = dart_sdk.dartx;
-  const hello_lib$ = hello_lib.hello_lib;
-  const hello = Object.create(null);
-  hello.main = function() {
-    return core.print(hello_lib$.greeting);
-  };
-  dart.fn(hello.main);
-  // Exports:
-  exports.hello = hello;
-});
-
-//# sourceMappingURL=hello.js.map
diff --git a/test/command/hello.js.map b/test/command/hello.js.map
deleted file mode 100644
index 35a47b8..0000000
--- a/test/command/hello.js.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"sourceRoot":"","sources":["hello.dart"],"names":["print","greeting"],"mappings":";;;;;;;;;;AAEA,eAAI,WAAG;UAAG,AAAAA,WAAK,CAACC,mBAAQ,CAAC;GACzB,AAAA;AAAA","file":"hello.js"}
\ No newline at end of file
diff --git a/test/command/hello.sum b/test/command/hello.sum
deleted file mode 100644
index 13fa193..0000000
--- a/test/command/hello.sum
+++ /dev/null
Binary files differ
diff --git a/test/command/hello_lib.dart b/test/command/hello_lib.dart
deleted file mode 100644
index 3251595..0000000
--- a/test/command/hello_lib.dart
+++ /dev/null
@@ -1 +0,0 @@
-String greeting = 'hello!';
diff --git a/test/command/hello_lib.js b/test/command/hello_lib.js
deleted file mode 100644
index 9bd68b7..0000000
--- a/test/command/hello_lib.js
+++ /dev/null
@@ -1,14 +0,0 @@
-dart_library.library('hello_lib', null, /* Imports */[
-  'dart_sdk'
-], function(exports, dart_sdk) {
-  'use strict';
-  const core = dart_sdk.core;
-  const dart = dart_sdk.dart;
-  const dartx = dart_sdk.dartx;
-  const hello_lib = Object.create(null);
-  hello_lib.greeting = 'hello!';
-  // Exports:
-  exports.hello_lib = hello_lib;
-});
-
-//# sourceMappingURL=hello_lib.js.map
diff --git a/test/command/hello_lib.js.map b/test/command/hello_lib.js.map
deleted file mode 100644
index 7d592bc..0000000
--- a/test/command/hello_lib.js.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"sourceRoot":"","sources":["hello_lib.dart"],"names":[],"mappings":";;;;;;;;AAAO,uBAAW,QAAQ;AAAA","file":"hello_lib.js"}
\ No newline at end of file
diff --git a/test/command/hello_lib.sum b/test/command/hello_lib.sum
deleted file mode 100644
index 2bfe850..0000000
--- a/test/command/hello_lib.sum
+++ /dev/null
Binary files differ
diff --git a/test/worker/worker_test.dart b/test/worker/worker_test.dart
new file mode 100644
index 0000000..bb10445
--- /dev/null
+++ b/test/worker/worker_test.dart
@@ -0,0 +1,150 @@
+// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:bazel_worker/bazel_worker.dart';
+// TODO(jakemac): Remove once this is a part of the testing library.
+import 'package:bazel_worker/src/async_message_grouper.dart';
+import 'package:bazel_worker/testing.dart';
+import 'package:test/test.dart';
+
+main() {
+  group('Hello World', () {
+    final inputDartFile = new File('test/worker/hello_world.dart').absolute;
+    final outputJsFile = new File('test/worker/hello_world.js').absolute;
+    final executableArgs = [
+      'bin/dartdevc.dart',
+      'compile',
+    ];
+    final compilerArgs = [
+      '--no-source-map',
+      '--no-summarize',
+      '-o',
+      outputJsFile.path,
+      inputDartFile.path,
+    ];
+
+    setUp(() {
+      inputDartFile.createSync();
+      inputDartFile.writeAsStringSync('main() => print("hello world");');
+    });
+
+    tearDown(() {
+      if (inputDartFile.existsSync()) inputDartFile.deleteSync();
+      if (outputJsFile.existsSync()) outputJsFile.deleteSync();
+    });
+
+    test('can compile in worker mode', () async {
+      var args = new List.from(executableArgs)..add('--persistent_worker');
+      var process = await Process.start('dart', args);
+      var messageGrouper = new AsyncMessageGrouper(process.stdout);
+
+      var request = new WorkRequest();
+      request.arguments.addAll(compilerArgs);
+      process.stdin.add(protoToDelimitedBuffer(request));
+
+      var response = await _readResponse(messageGrouper);
+      expect(response.exitCode, EXIT_CODE_OK, reason: response.output);
+      expect(response.output, isEmpty);
+
+      expect(outputJsFile.existsSync(), isTrue);
+      expect(outputJsFile.readAsStringSync(), contains('hello world'));
+
+      /// Now update hello_world.dart and send another [WorkRequest].
+      inputDartFile.writeAsStringSync('main() => print("goodbye world");');
+      process.stdin.add(protoToDelimitedBuffer(request));
+
+      response = await _readResponse(messageGrouper);
+      expect(response.exitCode, EXIT_CODE_OK, reason: response.output);
+      expect(response.output, isEmpty);
+      expect(outputJsFile.readAsStringSync(), contains('goodbye world'));
+
+      process.kill();
+
+      // TODO(jakemac): This shouldn't be necessary, but it is for the process
+      // to exit properly.
+      expect(await messageGrouper.next, isNull);
+    });
+
+    test('can compile in basic mode', () {
+      var args = new List.from(executableArgs)..addAll(compilerArgs);
+      var result = Process.runSync('dart', args);
+
+      expect(result.exitCode, EXIT_CODE_OK);
+      expect(result.stdout, isEmpty);
+      expect(result.stderr, isEmpty);
+      expect(outputJsFile.existsSync(), isTrue);
+    });
+  });
+
+  group('Hello World with Summaries', () {
+    final greetingDart = new File('test/worker/greeting.dart').absolute;
+    final helloDart = new File('test/worker/hello.dart').absolute;
+
+    final greetingJS = new File('test/worker/greeting.js').absolute;
+    final greetingSummary = new File('test/worker/greeting.sum').absolute;
+    final helloJS = new File('test/worker/hello_world.js').absolute;
+
+    setUp(() {
+      greetingDart.writeAsStringSync('String greeting = "hello";');
+      helloDart.writeAsStringSync(
+          'import "greeting.dart";'
+          'main() => print(greeting);');
+    });
+
+    tearDown(() {
+      if (greetingDart.existsSync()) greetingDart.deleteSync();
+      if (helloDart.existsSync()) helloDart.deleteSync();
+      if (greetingJS.existsSync()) greetingJS.deleteSync();
+      if (greetingSummary.existsSync()) greetingSummary.deleteSync();
+      if (helloJS.existsSync()) helloJS.deleteSync();
+    });
+
+    test('can compile in basic mode', () {
+      var result = Process.runSync('dart', [
+        'bin/dartdevc.dart',
+        'compile',
+        '--no-source-map',
+        '-o',
+        greetingJS.path,
+        greetingDart.path,
+      ]);
+      expect(result.exitCode, EXIT_CODE_OK);
+      expect(result.stdout, isEmpty);
+      expect(result.stderr, isEmpty);
+      expect(greetingJS.existsSync(), isTrue);
+      expect(greetingSummary.existsSync(), isTrue);
+
+      result = Process.runSync('dart', [
+        'bin/dartdevc.dart',
+        'compile',
+        '--no-source-map',
+        '--no-summarize',
+        '-s',
+        greetingSummary.path,
+        '-o',
+        helloJS.path,
+        helloDart.path,
+      ]);
+      expect(result.exitCode, EXIT_CODE_OK);
+      expect(result.stdout, isEmpty);
+      expect(result.stderr, isEmpty);
+      expect(helloJS.existsSync(), isTrue);
+    });
+  });
+}
+
+Future<WorkResponse> _readResponse(MessageGrouper messageGrouper) async {
+  var buffer = await messageGrouper.next;
+  try {
+    return new WorkResponse.fromBuffer(buffer);
+  } catch (_) {
+    throw 'Failed to parse response: \n'
+        'bytes: $buffer\n'
+        'String: ${new String.fromCharCodes(buffer)}\n';
+  }
+}
diff --git a/tool/command_test.sh b/tool/command_test.sh
deleted file mode 100755
index 6332cab..0000000
--- a/tool/command_test.sh
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/bin/bash
-
-set -e # bail on error
-
-# Some tests require being run from the package root
-# switch to the root directory of dev_compiler
-cd $( dirname "${BASH_SOURCE[0]}" )/..
-
-
-dartanalyzer --strong --build-mode \
-    --build-summary-output=test/command/hello_lib.sum \
-    --build-summary-only \
-    "file:///Users/jmesserly/src/dev_compiler/test/command/hello_lib.dart|/Users/jmesserly/src/dev_compiler/test/command/hello_lib.dart"
-
-#dartanalyzer --strong --build-mode \
-#    --build-summary-input=test/command/hello_lib.sum \
-#    "file:///Users/jmesserly/src/dev_compiler/test/command/hello.dart|/Users/jmesserly/src/dev_compiler/test/command/hello.dart"
-
-
-
-#dart -c ./bin/dartdevc.dart compile \
-#    -o test/command/hello_lib.js test/command/hello_lib.dart
-
-dart -c ./bin/dartdevc.dart compile \
-    -o test/command/hello.js \
-    -s test/command/hello_lib.sum test/command/hello.dart
diff --git a/tool/test.sh b/tool/test.sh
index 514f6ba..be87aa7 100755
--- a/tool/test.sh
+++ b/tool/test.sh
@@ -28,8 +28,6 @@
 unset COVERALLS_TOKEN
 pub run test:test test/all_tests.dart || fail
 
-./tool/command_test.sh || fail
-
 {
   fc=`find test -name "*.dart" |\
       xargs grep "/\*\S* should be \S*\*/" | wc -l`