diff --git a/.gitignore b/.gitignore index 228751d..368bb2e 100644 --- a/.gitignore +++ b/.gitignore @@ -68,7 +68,8 @@ ref/ references/ # Non-public directories (kept local only) -docs/paper/ +docs/paper/* +!docs/paper/figs/ docs/paper-temp/ examples/ experiments/ diff --git a/docs/paper/figs/architecture.png b/docs/paper/figs/architecture.png new file mode 100644 index 0000000..601e358 Binary files /dev/null and b/docs/paper/figs/architecture.png differ diff --git a/docs/paper/figs/awcp-demo1.png b/docs/paper/figs/awcp-demo1.png new file mode 100644 index 0000000..d190610 Binary files /dev/null and b/docs/paper/figs/awcp-demo1.png differ diff --git a/docs/paper/figs/awcp-demo2.png b/docs/paper/figs/awcp-demo2.png new file mode 100644 index 0000000..9d6041d Binary files /dev/null and b/docs/paper/figs/awcp-demo2.png differ diff --git a/docs/paper/figs/awcp-sequence-diagram.tex b/docs/paper/figs/awcp-sequence-diagram.tex new file mode 100644 index 0000000..b6447ac --- /dev/null +++ b/docs/paper/figs/awcp-sequence-diagram.tex @@ -0,0 +1,126 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% AWCP Protocol Sequence Diagram - Professional UML 2.0 Style +% +% Required: \usepackage{tikz} +% \usetikzlibrary{positioning, arrows.meta, fit, backgrounds, calc} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\begin{figure}[!htb] +\centering +\resizebox{0.6\textwidth}{!}{% +\begin{tikzpicture}[ + % === Core styles === + participant/.style={rectangle, draw=black, thick, fill=white, + minimum width=2.2cm, minimum height=0.7cm, font=\small\bfseries}, + lifeline/.style={dashed, gray!60}, + % === Message arrows === + msg/.style={-{Stealth[length=2mm, width=1.5mm]}, thick}, + ret/.style={-{Stealth[length=2mm, width=1.5mm]}, thick, dashed}, + sse/.style={-{Stealth[length=2mm, width=1.5mm]}, thick, blue!70}, + % === Labels === + mlabel/.style={font=\scriptsize\ttfamily, fill=white, inner sep=1.5pt}, + plabel/.style={font=\scriptsize\itshape, text=black!70}, + % === Fragment (UML combined fragment) === + fragment/.style={draw=black!60, fill=white}, + fraglabel/.style={font=\scriptsize\bfseries, fill=black!10, draw=black!60, + inner sep=2pt, anchor=north west} +] + +% ========== Participants ========== +\node[participant] (D) at (0, 0) {Delegator}; +\node[participant] (E) at (6, 0) {Executor}; + +% ========== Lifelines ========== +\draw[lifeline] (0, -0.35) -- (0, -13.02); +\draw[lifeline] (6, -0.35) -- (6, -13.02); + +%========================================================================== +% PHASE 1: Negotiation +%========================================================================== +\begin{scope}[on background layer] + \draw[fragment] (-1.2, -0.6) rectangle (7.2, -2.8); +\end{scope} +\node[fraglabel] at (-1.2, -0.6) {Negotiation}; + +% INVITE +\draw[msg] (0, -1.2) -- node[mlabel, above] {INVITE} (6, -1.2); +\node[plabel] at (3, -1.5) {task, lease, environment}; + +% ACCEPT (response) +\draw[ret] (6, -2.2) -- node[mlabel, above] {ACCEPT} (0, -2.2); +\node[plabel] at (3, -2.5) {workDir, constraints}; + +%========================================================================== +% PHASE 2: Provisioning +%========================================================================== +\begin{scope}[on background layer] + \draw[fragment] (-1.2, -3.0) rectangle (7.2, -5.8); +\end{scope} +\node[fraglabel] at (-1.2, -3.0) {Provisioning}; + +% Self-call: Prepare Transport (proper UML self-message) +\draw[msg] (0, -3.6) -- ++(0.8, 0) -- ++(0, -0.3) -- ++(-0.8, 0); +\node[mlabel, anchor=west] at (0.9, -3.7) {Prepare Transport}; + +% START +\draw[msg] (0, -4.5) -- node[mlabel, above] {START} (6, -4.5); +\node[plabel] at (3, -4.8) {lease, workDir (credentials)}; + +% {ok} response +\draw[ret] (6, -5.5) -- node[mlabel, above] {\{ok: true\}} (0, -5.5); + +%========================================================================== +% PHASE 3: Execution (SSE Stream) +%========================================================================== +\begin{scope}[on background layer] + \draw[fragment] (-1.2, -6.0) rectangle (7.2, -11.1); +\end{scope} +\node[fraglabel] at (-1.2, -6.0) {Execution}; + +% Self-call: Setup Workspace +\draw[msg] (6, -6.3) -- ++(-0.8, 0) -- ++(0, -0.3) -- ++(0.8, 0); +\node[mlabel, anchor=east] at (5.1, -6.4) {Setup Workspace}; + +% SSE subscription +\draw[sse] (0, -7.2) -- node[mlabel, above] {SSE: /tasks/:taskId/events} (6, -7.2); + +% status event +\draw[sse] (6, -7.9) -- node[mlabel, above] {event: status} (0, -7.9); +\node[plabel] at (3, -8.2) {running}; + +% Self-call: Execute Task +\draw[msg] (6, -8.7) -- ++(-0.8, 0) -- ++(0, -0.3) -- ++(0.8, 0); +\node[mlabel, anchor=east] at (5.1, -8.8) {Execute Task}; + +% Self-call: Teardown +\draw[msg] (6, -9.5) -- ++(-0.8, 0) -- ++(0, -0.3) -- ++(0.8, 0); +\node[mlabel, anchor=east] at (5.1, -9.6) {Teardown}; + +% snapshot event +\draw[sse] (6, -10.2) -- node[mlabel, above] {event: snapshot} (0, -10.2); +\node[plabel] at (3, -10.5) {snapshotId, data, recommended}; + +% done event +\draw[sse] (6, -10.9) -- node[mlabel, above] {event: done} (0, -10.9); + +%========================================================================== +% PHASE 4: Completion +%========================================================================== +\begin{scope}[on background layer] + \draw[fragment] (-1.2, -11.3) rectangle (7.2, -13.0); +\end{scope} +\node[fraglabel] at (-1.2, -11.3) {Completion}; + +% Self-call: Apply Snapshot +\draw[msg] (0, -11.9) -- ++(0.8, 0) -- ++(0, -0.3) -- ++(-0.8, 0); +\node[mlabel, anchor=west] at (0.9, -12.0) {Apply Snapshot}; + +% ACK +\draw[msg] (0, -12.7) -- node[mlabel, above] {ACK} (6, -12.7); + +\end{tikzpicture}% +} +\caption{AWCP four-phase message sequence between Delegator and Executor. +Solid, dashed, and blue arrows denote synchronous HTTP requests, responses, and asynchronous SSE events, respectively.} +\label{fig:awcp-protocol} +\end{figure} diff --git a/docs/paper/figs/protocol-landscape.tex b/docs/paper/figs/protocol-landscape.tex new file mode 100644 index 0000000..a8d7c6c --- /dev/null +++ b/docs/paper/figs/protocol-landscape.tex @@ -0,0 +1,101 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% AWCP Protocol Landscape - Relationship with MCP, A2A, and other protocols +% +% Required: \usepackage{tikz} +% \usetikzlibrary{positioning, arrows.meta, fit, backgrounds, calc} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\begin{figure}[t] +\centering +\resizebox{0.92\textwidth}{!}{% +\begin{tikzpicture}[ + % === Node styles === + agent/.style={rectangle, rounded corners=6pt, draw=black!35, thick, + fill=green!5, minimum width=2.0cm, minimum height=1.3cm, + font=\small\bfseries, align=center}, + svc/.style={rectangle, rounded corners=5pt, draw=black!45, thick, + fill=blue!6, minimum width=2.6cm, minimum height=1.3cm, + font=\small\bfseries, align=center}, + pill/.style={rectangle, rounded corners=3pt, draw=black!20, + inner sep=4pt, font=\scriptsize\bfseries, text=black!65}, + % === Arrow styles === + arr/.style={-{Stealth[length=2.5mm, width=2mm]}, thick, black!55}, + % === Label styles === + desc/.style={font=\scriptsize, text=black!40, align=center} +] + +% ===================================================================== +% Left column: User + Delegator Agent +% ===================================================================== +\node[agent, fill=yellow!6, draw=black!30] (user) at (0, 0.4) {User}; +\node[agent] (dagent) at (0, -2.0) {Delegator\\Agent}; +\draw[arr] (user) -- (dagent); + +% ===================================================================== +% Center: AWCP boundary +% ===================================================================== + +% --- Services --- +\node[svc] (dserv) at (6.2, -0.2) {Delegator\\Service}; +\node[svc] (eserv) at (11.4, -0.2) {Executor\\Service}; + +% --- Control plane: bidirectional HTTP/SSE between services --- +\draw[{Stealth[length=2.5mm, width=2mm]}-{Stealth[length=2.5mm, width=2mm]}, + thick, black!40, densely dashed] (dserv.east) -- (eserv.west); +\node[desc] at (8.8, 0.25) {HTTP / SSE}; + +% --- Transport plane (below services) --- +\node[pill, fill=orange!10, draw=orange!30, text=orange!70!black] + (trans) at (8.8, -2.2) + {SSHFS~~$\vert$~~Archive~~$\vert$~~Storage~~$\vert$~~Git}; +\node[desc] at (8.8, -2.75) {Workspace Transport}; +% --- Transport connection: bus-style vertical stubs + horizontal bar --- +\coordinate (busY) at (0, -1.25); % y-level of horizontal bus +\draw[orange!55, line width=1pt] + (dserv.south) -- (dserv.south |- busY); +\draw[orange!55, line width=1pt] + (eserv.south) -- (eserv.south |- busY); +\draw[orange!55, line width=1pt] + (dserv.south |- busY) -- (eserv.south |- busY); +\draw[arr, orange!55, line width=1pt] + (8.8, -1.25) -- (trans.north); + +% --- AWCP boundary box (enlarged, label outside) --- +\begin{scope}[on background layer] + \draw[rounded corners=10pt, draw=orange!40, thick, + fill=orange!3, densely dashed] + (4.3, 1.2) rectangle (13.3, -3.3); +\end{scope} +\node[font=\footnotesize\bfseries, text=orange!60!black, anchor=south west] + at (4.45, 1.25) {AWCP}; + +% ===================================================================== +% Right column: Executor Agent +% ===================================================================== +\node[agent] (eagent) at (17.6, -2.0) {Executor\\Agent}; + +% ===================================================================== +% Connections: Agents ↔ AWCP services +% ===================================================================== + +% --- Delegator Agent → Delegator Service --- +\draw[arr] (dagent.east) -- ++(1.6, 0) |- (dserv.west); + +% Integration pill: positioned in the gap, clearly left of AWCP border +\node[pill, fill=purple!6] at (2.5, -1.2) + {MCP~~$\vert$~~Skill~~$\vert$~~Tools}; +\node[desc] at (2.5, -1.7) {Integration Interfaces}; + +% --- Executor Service → Executor Agent --- +\draw[arr] (eserv.east) -- ++(1.6, 0) |- (eagent.west); + +% Adapter pill: positioned in the gap, clearly right of AWCP border +\node[pill, fill=purple!6] at (15.1, -1.2) + {A2A~~$\vert$~~ANP~~$\vert$~~HTTP}; +\node[desc] at (15.1, -1.7) {Adapter Layer}; + +\end{tikzpicture}% +} +\caption{AWCP in the agentic protocol landscape. Delegator agents access AWCP through integration interfaces such as MCP tool servers or skill modules. The control plane uses standard HTTP and Server-Sent Events for signaling between AWCP services. Executor agents are invoked through an extensible adapter layer supporting A2A, ANP, or direct HTTP. The transport plane provides pluggable filesystem-level access that complements message-level coordination.} +\label{fig:protocol-landscape} +\end{figure} diff --git a/docs/paper/figs/state-machines.tex b/docs/paper/figs/state-machines.tex new file mode 100644 index 0000000..6e4dd14 --- /dev/null +++ b/docs/paper/figs/state-machines.tex @@ -0,0 +1,135 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% AWCP Dual State Machines - Delegation (Delegator) and Assignment (Executor) +% +% Required: \usepackage{tikz} +% \usetikzlibrary{positioning, arrows.meta, fit, backgrounds, calc} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\begin{figure}[t] +\centering +\resizebox{0.88\textwidth}{!}{% +\begin{tikzpicture}[ + % === State styles === + state/.style={rectangle, rounded corners=5pt, draw=black!60, thick, + fill=white, minimum width=1.8cm, minimum height=0.75cm, + font=\small, align=center}, + init/.style={state, fill=blue!6}, + terminal/.style={state, fill=black!8, draw=black!40, text=black!65}, + % === Arrow styles === + trans/.style={-{Stealth[length=2.2mm, width=1.8mm]}, thick, black!65}, + sync/.style={-{Stealth[length=2.2mm, width=1.8mm]}, thick, blue!55, + densely dashed}, + % === Label styles === + evlabel/.style={font=\scriptsize\ttfamily, fill=white, inner sep=1.5pt, + text=black!70}, + synclabel/.style={font=\scriptsize\itshape, fill=white, inner sep=1.5pt, + text=blue!60}, + fraglabel/.style={font=\small\bfseries, text=black!60}, + % === Fragment === + fragment/.style={draw=black!20, rounded corners=8pt, fill=black!2} +] + +% === Vertical spacing === +\def\ystep{1.55} + +%========================================================================== +% LEFT: DelegationStateMachine (Delegator side) +%========================================================================== + +% Happy-path states — vertical spine +\node[init] (d-created) at (0, 0) {\texttt{created}}; +\node[state] (d-invited) at (0, -1*\ystep) {\texttt{invited}}; +\node[state] (d-accepted) at (0, -2*\ystep) {\texttt{accepted}}; +\node[state] (d-started) at (0, -3*\ystep) {\texttt{started}}; +\node[state] (d-running) at (0, -4*\ystep) {\texttt{running}}; +\node[terminal] (d-completed) at (0, -5*\ystep) {\texttt{completed}}; + +% Happy-path transitions +\draw[trans] (d-created) -- node[evlabel, right, xshift=1pt] {SEND\_INVITE} (d-invited); +\draw[trans] (d-invited) -- node[evlabel, right, xshift=1pt] {RECV\_ACCEPT} (d-accepted); +\draw[trans] (d-accepted) -- node[evlabel, right, xshift=1pt] {SEND\_START} (d-started); +\draw[trans] (d-started) -- node[evlabel, right, xshift=1pt] {SETUP\_COMPLETE} (d-running); +\draw[trans] (d-running) -- node[evlabel, right, xshift=1pt] {RECV\_DONE} (d-completed); + +% Terminal states — grouped to the left, generous vertical spacing +\node[terminal] (d-error) at (-2.6, -5*\ystep) {\texttt{error}}; +\node[terminal] (d-cancelled) at (-2.6, -5*\ystep-1.1) {\texttt{cancelled}}; +\node[terminal] (d-expired) at (-2.6, -5*\ystep-2.2) {\texttt{expired}}; + +% Single aggregate arrow for exceptional transitions +\draw[-{Stealth[length=2.2mm, width=1.8mm]}, thick, red!40, dashed] + (-1.15, -0.5*\ystep) -- (-1.15, -4.5*\ystep) -- (d-error.north east); +\node[font=\scriptsize, text=red!45, align=left, anchor=east] + at (-1.25, -2.5*\ystep) {\textit{any non-terminal}}; + +% Annotation for cancelled/expired +\node[font=\scriptsize, text=black!40, align=center, anchor=north] + at (-2.6, -5*\ystep-3.1) {via \textsc{error} / \textsc{cancel} / \textsc{expire}}; + +% Fragment box +\begin{scope}[on background layer] + \draw[fragment] (-4.05, 1.55) rectangle (1.85, -5*\ystep-3.85); +\end{scope} +\node[fraglabel] at (-1.1, 1.2) {DelegationStateMachine}; +\node[font=\scriptsize, text=black!40] at (-1.1, 0.8) {(Delegator side)}; + +%========================================================================== +% RIGHT: AssignmentStateMachine (Executor side) +%========================================================================== + +% Align sync points: pending at invited level, active at started level, +% completed at running level +\def\xr{6.2} + +\node[init] (e-pending) at (\xr, -1*\ystep) {\texttt{pending}}; +\node[state] (e-active) at (\xr, -3*\ystep) {\texttt{active}}; +\node[terminal] (e-completed) at (\xr, -5*\ystep) {\texttt{completed}}; +\node[terminal] (e-error) at ({\xr+2.2}, -5*\ystep) {\texttt{error}}; + +% Happy-path transitions +\draw[trans] (e-pending) -- node[evlabel, left, xshift=-1pt] {RECV\_START} (e-active); +\draw[trans] (e-active) -- node[evlabel, left, xshift=-1pt] {TASK\_COMPLETE} (e-completed); + +% Error transitions — single aggregate arrow +\draw[-{Stealth[length=2.2mm, width=1.8mm]}, thick, red!40, dashed] + ({\xr+1.15}, -1.5*\ystep) -- ({\xr+1.15}, -4.5*\ystep) -- (e-error.north west); +\node[font=\scriptsize, text=red!45, align=right, anchor=west] + at ({\xr+1.25}, -3*\ystep) {\textit{any non-}\\\textit{terminal}}; + +% Annotation +\node[font=\scriptsize, text=black!40, align=center, anchor=north] + at ({\xr+2.2}, -5*\ystep-0.7) {via \textsc{error} / \textsc{cancel}}; + +% Fragment box +\begin{scope}[on background layer] + \draw[fragment] ({\xr-1.75}, 1.55) rectangle ({\xr+3.55}, -5*\ystep-1.4); +\end{scope} +\node[fraglabel] at ({\xr+0.9}, 1.2) {AssignmentStateMachine}; +\node[font=\scriptsize, text=black!40] at ({\xr+0.9}, 0.8) {(Executor side)}; + +%========================================================================== +% Synchronization arrows (horizontal, between aligned states) +%========================================================================== + +% ACCEPT: Executor sends ACCEPT → Delegator transitions invited→accepted, +% Executor creates assignment in pending +\draw[sync] (d-invited.east) ++(0.15,0) -- + node[synclabel, above] {\textnormal{\texttt{ACCEPT}}} + ([xshift=-0.15cm]e-pending.west); + +% START: Delegator sends START → triggers pending→active +\draw[sync] (d-accepted.east) ++(0.15,0) -- ++(0.6,0) |- + node[synclabel, above, pos=0.75] {\textnormal{\texttt{START}}} + ([xshift=-0.15cm]e-active.west); + +% DONE: Executor sends DONE → Delegator transitions running→completed +\draw[{Stealth[length=2.2mm, width=1.8mm]}-, thick, blue!55, densely dashed] + (d-running.east) ++(0.15,0) -- ++(0.6,0) |- + node[synclabel, above, pos=0.75] {\textnormal{\texttt{DONE}}} + ([xshift=-0.15cm]e-completed.west); + +\end{tikzpicture}% +} +\caption{Dual state machines governing the AWCP delegation lifecycle. The \textit{DelegationStateMachine}~(left) tracks nine states on the Delegator side; the \textit{AssignmentStateMachine}~(right) tracks four states on the Executor side. Solid arrows trace the happy path; a single dashed red path indicates that any non-terminal state may transition to a terminal state upon error, cancellation, or lease expiration. Blue dashed arrows mark cross-machine synchronization via protocol messages.} +\label{fig:state-machines} +\end{figure} diff --git a/package-lock.json b/package-lock.json index 2421fb7..12f5937 100644 --- a/package-lock.json +++ b/package-lock.json @@ -463,7 +463,6 @@ "experiments/scenarios/01-local-basic": { "name": "@awcp-experiments/01-local-basic", "version": "0.0.1", - "extraneous": true, "dependencies": { "@awcp/sdk": "*" }, @@ -474,7 +473,6 @@ "experiments/scenarios/02-admission-test": { "name": "@awcp-experiments/02-admission-test", "version": "0.0.1", - "extraneous": true, "dependencies": { "@awcp/core": "*", "@awcp/sdk": "*" @@ -486,7 +484,6 @@ "experiments/scenarios/03-mcp-integration": { "name": "@awcp-experiments/03-mcp-integration", "version": "0.0.1", - "extraneous": true, "dependencies": { "@awcp/sdk": "*", "@awcp/transport-archive": "*", @@ -561,6 +558,7 @@ }, "experiments/scenarios/09-multimodal-test": { "version": "1.0.0", + "extraneous": true, "dependencies": { "@modelcontextprotocol/sdk": "^1.0.0" } @@ -568,7 +566,6 @@ "experiments/shared/executor-agent": { "name": "awcp-executor-agent", "version": "0.1.0", - "extraneous": true, "dependencies": { "@a2a-js/sdk": "^0.3.5", "@awcp/sdk": "*", @@ -632,6 +629,18 @@ "resolved": "examples/vision-executor", "link": true }, + "node_modules/@awcp-experiments/01-local-basic": { + "resolved": "experiments/scenarios/01-local-basic", + "link": true + }, + "node_modules/@awcp-experiments/02-admission-test": { + "resolved": "experiments/scenarios/02-admission-test", + "link": true + }, + "node_modules/@awcp-experiments/03-mcp-integration": { + "resolved": "experiments/scenarios/03-mcp-integration", + "link": true + }, "node_modules/@awcp-experiments/04-archive-transport": { "resolved": "experiments/scenarios/04-archive-transport", "link": true @@ -2883,10 +2892,6 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/09-multimodal-test": { - "resolved": "experiments/scenarios/09-multimodal-test", - "link": true - }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -3150,6 +3155,10 @@ "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", "license": "MIT" }, + "node_modules/awcp-executor-agent": { + "resolved": "experiments/shared/executor-agent", + "link": true + }, "node_modules/b4a": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", @@ -7483,13 +7492,14 @@ "dependencies": { "@a2a-js/sdk": "^0.3.5", "@awcp/core": "^0.0.18", - "ws": "^8.19.0" + "ws": "^8.18.0" }, "bin": { "delegator-daemon": "dist/delegator/bin/daemon.js" }, "devDependencies": { - "@types/ws": "^8.18.1" + "@types/express": "^4.17.21", + "@types/ws": "^8.5.13" }, "peerDependencies": { "express": "^4.18.0"