Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ flowchart LR
| Agent Runtime Contract | Forge agent manifest와 Runtime `result.agent` 참조를 사용하는 Vision / Voice-Command / Safety-Monitor dummy workload | `configs/agent_3_workload_demo.json`, [`docs/agent_orchestration_summary_contract.ko.md`](docs/agent_orchestration_summary_contract.ko.md) |
| Lightweight Sustained Workload Starter | YOLO-like vision, Whisper-like command burst, FastAPI-style ingress, optional tegrastats timeline, producer-backed starter를 포함한 profiled local sustained scenario | `python3 -m inferedge_orchestrator run-multi-workload-sustained ...` |
| Device-Local Sustained Starter | committed image/request/resource snapshot producer를 하나의 `device_local` mode로 실행하는 starter | `configs/agent_multi_workload_sustained_device_local.json` |
| Remote Dispatch Starter | production remote execution을 주장하지 않고 file-based worker registry와 task request contract로 remote edge worker selection을 재현 | [`docs/remote_dispatch_starter.ko.md`](docs/remote_dispatch_starter.ko.md) |
| Remote Dispatch Starter | production remote execution을 주장하지 않고 file-based worker registry와 task request contract로 remote edge worker selection을 재현하며 local HTTP worker starter로 명시적 starter 실행을 검증 | [`docs/remote_dispatch_starter.ko.md`](docs/remote_dispatch_starter.ko.md) |

## Validation Evidence

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ The boundary is intentional:
| Sustained Agent Scenario Starter | Normal / overload / sustained-high-load 3-agent modes with queue-depth timeline, latency timeline, and policy decision reasons | `configs/agent_3_workload_sustained_high_load.json` |
| Lightweight Sustained Workload Starter | Profiled local sustained scenario for YOLO-like vision, Whisper-like command burst, FastAPI-style ingress, optional tegrastats timeline, and producer-backed starters | `python3 -m inferedge_orchestrator run-multi-workload-sustained ...` |
| Device-Local Sustained Starter | Device-local mode using committed image, request, and resource snapshot producers before live device integrations | `configs/agent_multi_workload_sustained_device_local.json` |
| Remote Dispatch Starter | File-based worker registry and task request contract for selecting a remote edge worker; optional `--execute-plan` records explicit HTTP/SSH starter execution evidence without claiming production remote execution | [`docs/remote_dispatch_starter.md`](docs/remote_dispatch_starter.md) |
| Remote Dispatch Starter | File-based worker registry and task request contract for selecting a remote edge worker; optional `--execute-plan` records explicit HTTP/SSH starter evidence with a local HTTP worker starter without claiming production remote execution | [`docs/remote_dispatch_starter.md`](docs/remote_dispatch_starter.md) |

## Validation Evidence

Expand Down
26 changes: 26 additions & 0 deletions docs/remote_dispatch_starter.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ Task request:

- schema: `inferedge-remote-task-request-v1`
- 예시: [`examples/remote_task_request.json`](../examples/remote_task_request.json)
- local HTTP 예시:
[`examples/remote_task_request_http_local.json`](../examples/remote_task_request_http_local.json)

starter는 다음 항목을 기준으로 worker를 선택한다.

Expand Down Expand Up @@ -61,6 +63,28 @@ python3 -m inferedge_orchestrator remote-dispatch \
--timeout-sec 5
```

Local HTTP worker starter:

```bash
python3 scripts/remote_http_worker.py --host 127.0.0.1 --port 8765
```

다른 터미널에서:

```bash
python3 -m inferedge_orchestrator remote-dispatch \
--registry examples/remote_worker_registry_http_local.json \
--request examples/remote_task_request_http_local.json \
--output reports/remote_dispatch_http_local.json \
--execute-plan \
--timeout-sec 2
```

이 경로는 의도적으로 작게 유지한다. HTTP worker endpoint가 structured task
request를 받고 structured starter response를 반환할 수 있음을 검증하지만,
heartbeat, auth, fallback worker에 대한 retry execution, long-lived production
worker process를 제공하지 않는다.

예상 출력:

```json
Expand Down Expand Up @@ -104,6 +128,8 @@ starter는 기본적으로 execution planning만 기록하고 network connection

- `http_request`는 task request를 `metadata.endpoint_url`로 POST한다.
- `ssh_command`는 `metadata.ssh_host`에서 `metadata.ssh_command`를 실행한다.
- `scripts/remote_http_worker.py`는 repeatable success-path smoke validation을
위한 local HTTP starter endpoint를 제공한다.
- timeout, connection failure, HTTP error, command failure는 unstructured crash가
아니라 `remote_execution_result`에 기록된다.
- fallback execution은 아직 자동 수행하지 않는다. fallback candidate는 이후
Expand Down
26 changes: 26 additions & 0 deletions docs/remote_dispatch_starter.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ Task request:

- schema: `inferedge-remote-task-request-v1`
- example: [`examples/remote_task_request.json`](../examples/remote_task_request.json)
- local HTTP example:
[`examples/remote_task_request_http_local.json`](../examples/remote_task_request_http_local.json)

The starter matches:

Expand Down Expand Up @@ -62,6 +64,28 @@ python3 -m inferedge_orchestrator remote-dispatch \
--timeout-sec 5
```

Local HTTP worker starter:

```bash
python3 scripts/remote_http_worker.py --host 127.0.0.1 --port 8765
```

In another terminal:

```bash
python3 -m inferedge_orchestrator remote-dispatch \
--registry examples/remote_worker_registry_http_local.json \
--request examples/remote_task_request_http_local.json \
--output reports/remote_dispatch_http_local.json \
--execute-plan \
--timeout-sec 2
```

This path is intentionally small: it proves that an HTTP worker endpoint can
receive a structured task request and return a structured starter response. It
does not provide heartbeat, auth, retry execution against fallback workers, or a
long-lived production worker process.

Expected output:

```json
Expand Down Expand Up @@ -105,6 +129,8 @@ When execution is requested:

- `http_request` posts the task request to `metadata.endpoint_url`.
- `ssh_command` runs `metadata.ssh_command` on `metadata.ssh_host`.
- `scripts/remote_http_worker.py` provides a local HTTP starter endpoint for
repeatable success-path smoke validation.
- timeout, connection failure, HTTP error, and command failure are recorded in
`remote_execution_result` instead of raising an unstructured crash.
- fallback execution is not automatic yet; fallback candidates remain recorded
Expand Down
18 changes: 18 additions & 0 deletions examples/remote_task_request_http_local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"schema_version": "inferedge-remote-task-request-v1",
"task_id": "task_http_local_001",
"agent_id": "vision_agent",
"agent_type": "vision",
"required_backend": "onnxruntime",
"device_target": "cpu",
"priority": 80,
"latency_budget_ms": 50.0,
"input_ref": {
"type": "image",
"path": "examples/inputs/vision_frame.ppm"
},
"retry_policy": {
"max_attempts": 1,
"fallback_on": ["timeout", "connection_error"]
}
}
24 changes: 24 additions & 0 deletions examples/remote_worker_registry_http_local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"schema_version": "inferedge-remote-worker-registry-v1",
"workers": [
{
"worker_id": "local-http-worker",
"status": "online",
"endpoint_type": "http_request",
"capabilities": {
"workers": ["onnxruntime", "dummy"],
"backends": ["onnxruntime", "dummy"],
"devices": ["cpu"],
"priority_capacity": 4
},
"health": {
"state": "healthy",
"last_seen_at": "2026-05-20T00:00:00Z"
},
"metadata": {
"endpoint_url": "http://127.0.0.1:8765/execute",
"note": "local HTTP starter endpoint; not production remote execution"
}
}
]
}
135 changes: 135 additions & 0 deletions scripts/remote_http_worker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#!/usr/bin/env python3
from __future__ import annotations

import argparse
import json
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from typing import Any


RESPONSE_SCHEMA_VERSION = "inferedge-remote-http-worker-response-v1"


def make_handler(worker_id: str) -> type[BaseHTTPRequestHandler]:
class RemoteHttpWorkerHandler(BaseHTTPRequestHandler):
server_version = "InferEdgeRemoteHttpWorker/0.1"

def do_GET(self) -> None:
if self.path != "/health":
self._write_json(
404,
{
"schema_version": RESPONSE_SCHEMA_VERSION,
"status": "not_found",
"worker_id": worker_id,
},
)
return
self._write_json(
200,
{
"schema_version": RESPONSE_SCHEMA_VERSION,
"status": "healthy",
"worker_id": worker_id,
"production_remote_execution": False,
},
)

def do_POST(self) -> None:
if self.path not in {"/", "/execute"}:
self._write_json(
404,
{
"schema_version": RESPONSE_SCHEMA_VERSION,
"status": "not_found",
"worker_id": worker_id,
},
)
return

try:
content_length = int(self.headers.get("Content-Length", "0"))
except ValueError:
content_length = 0
raw_body = self.rfile.read(content_length).decode("utf-8")
try:
payload = json.loads(raw_body) if raw_body else {}
except json.JSONDecodeError as exc:
self._write_json(
400,
{
"schema_version": RESPONSE_SCHEMA_VERSION,
"status": "invalid_json",
"worker_id": worker_id,
"error": str(exc),
},
)
return

task_request = payload.get("task_request", {})
if not isinstance(task_request, dict):
self._write_json(
400,
{
"schema_version": RESPONSE_SCHEMA_VERSION,
"status": "invalid_task_request",
"worker_id": worker_id,
},
)
return

self._write_json(
200,
{
"schema_version": RESPONSE_SCHEMA_VERSION,
"status": "accepted",
"execution_status": "simulated_completed",
"worker_id": payload.get("worker_id", worker_id),
"task_id": task_request.get("task_id"),
"agent_id": task_request.get("agent_id"),
"required_backend": task_request.get("required_backend"),
"device_target": task_request.get("device_target"),
"production_remote_execution": False,
"note": (
"Local HTTP remote worker starter response only; not a "
"long-lived production worker."
),
},
)

def log_message(self, format: str, *args: Any) -> None:
return

def _write_json(self, status_code: int, payload: dict[str, Any]) -> None:
body = json.dumps(payload, sort_keys=True).encode("utf-8")
self.send_response(status_code)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)

return RemoteHttpWorkerHandler


def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(
description="Run a local HTTP remote worker starter for InferEdgeOrchestrator."
)
parser.add_argument("--host", default="127.0.0.1")
parser.add_argument("--port", type=int, default=8765)
parser.add_argument("--worker-id", default="local-http-worker")
args = parser.parse_args(argv)

server = ThreadingHTTPServer((args.host, args.port), make_handler(args.worker_id))
print(f"remote_http_worker listening on http://{args.host}:{args.port}/execute")
try:
server.serve_forever()
except KeyboardInterrupt:
pass
finally:
server.server_close()
return 0


if __name__ == "__main__":
raise SystemExit(main())
Loading