@@ -171,6 +171,7 @@ Connect to pre-started server. API key required.
171171| POST | ` /api/v1/sessions/{id}/execute ` | Execute code |
172172| GET | ` /api/v1/sessions/{id}/stream/{exec_id} ` | SSE stream |
173173| POST | ` /api/v1/sessions/{id}/variables ` | Update variables |
174+ | POST | ` /api/v1/sessions/{id}/files ` | Upload file to session cwd |
174175| GET | ` /api/v1/sessions/{id}/artifacts/{file} ` | Download artifact |
175176
176177## Usage
@@ -236,6 +237,140 @@ with ExecutionClient(
2362374 . ** Streaming** : SSE events for stdout/stderr during execution
2372385 . ** Session Cleanup** : ` DELETE /sessions/{id} ` → Environment.stop_session()
238239
240+ ## File Upload Flow
241+
242+ File upload enables the ` /load ` CLI command to transfer files from the client machine to the execution server's working directory. This is essential when the execution server runs in a container or on a remote machine where the client's local filesystem is not accessible.
243+
244+ ### Architecture
245+
246+ ```
247+ ┌─────────────────────────────────────────────────────────────────────────────┐
248+ │ TASKWEAVER CLIENT │
249+ │ │
250+ │ ┌─────────────┐ ┌─────────────┐ ┌───────────────────────────────┐ │
251+ │ │ Session │───▶│ _upload_file│───▶│ ExecutionServiceClient │ │
252+ │ │ /load cmd │ │ (lazy) │ │ .upload_file() │ │
253+ │ └─────────────┘ └─────────────┘ └───────────────┬───────────────┘ │
254+ │ │ │
255+ │ ▼ │
256+ │ ┌───────────────────────────────┐ │
257+ │ │ ExecutionClient.upload_file() │ │
258+ │ │ - Read file content │ │
259+ │ │ - Base64 encode │ │
260+ │ │ - HTTP POST │ │
261+ │ └───────────────┬───────────────┘ │
262+ └────────────────────────────────────────────────────────┼────────────────────┘
263+ │
264+ │ POST /api/v1/sessions/{id}/files
265+ │ {filename, content (base64), encoding}
266+ ▼
267+ ┌─────────────────────────────────────────────────────────────────────────────┐
268+ │ EXECUTION SERVER │
269+ │ │
270+ │ ┌───────────────────────────────────────────────────────────────────────┐ │
271+ │ │ routes.upload_file() │ │
272+ │ │ - Validate session exists │ │
273+ │ │ - Base64 decode content │ │
274+ │ │ - Call session_manager.upload_file() │ │
275+ │ └───────────────────────────────────────────────────────────────────────┘ │
276+ │ │ │
277+ │ ▼ │
278+ │ ┌───────────────────────────────────────────────────────────────────────┐ │
279+ │ │ ServerSessionManager.upload_file() │ │
280+ │ │ - Sanitize filename (prevent path traversal) │ │
281+ │ │ - Write to {session.cwd}/{filename} │ │
282+ │ │ - Return full path │ │
283+ │ └───────────────────────────────────────────────────────────────────────┘ │
284+ │ │ │
285+ │ ▼ │
286+ │ ┌───────────────────────────────────────────────────────────────────────┐ │
287+ │ │ Session Working Directory │ │
288+ │ │ /workspace/{session_id}/cwd/ │ │
289+ │ │ └── uploaded_file.csv │ │
290+ │ └───────────────────────────────────────────────────────────────────────┘ │
291+ └─────────────────────────────────────────────────────────────────────────────┘
292+ ```
293+
294+ ### Request/Response Models
295+
296+ ``` python
297+ # Request (server/models.py)
298+ class UploadFileRequest (BaseModel ):
299+ filename: str # Target filename (basename extracted, path traversal prevented)
300+ content: str # File content (base64 encoded for binary)
301+ encoding: Literal[" base64" , " text" ] = " base64"
302+
303+ # Response (server/models.py)
304+ class UploadFileResponse (BaseModel ):
305+ filename: str # Uploaded filename
306+ status: Literal[" uploaded" ] = " uploaded"
307+ path: str # Full path where file was saved on server
308+ ```
309+
310+ ### Client Usage
311+
312+ ``` python
313+ from taskweaver.ces.client import ExecutionClient
314+
315+ with ExecutionClient(session_id = " my-session" , server_url = " http://localhost:8000" ) as client:
316+ client.start()
317+
318+ # Upload a file
319+ with open (" /local/path/data.csv" , " rb" ) as f:
320+ content = f.read()
321+ saved_path = client.upload_file(" data.csv" , content)
322+
323+ # Now the file is available in the session's cwd
324+ result = client.execute_code(" exec-1" , " import pandas as pd; df = pd.read_csv('data.csv')" )
325+ ```
326+
327+ ### Session Integration
328+
329+ The ` Session ` class uses a lazily-initialized upload client:
330+
331+ ``` python
332+ # In taskweaver/session/session.py
333+ class Session :
334+ def _get_upload_client (self ):
335+ """ Lazy client creation - only created when first upload occurs."""
336+ if not hasattr (self , " _upload_client" ):
337+ self ._upload_client = self .exec_mgr.get_session_client(
338+ self .session_id,
339+ session_dir = self .workspace,
340+ cwd = self .execution_cwd,
341+ )
342+ self ._upload_client_started = False
343+
344+ if not self ._upload_client_started:
345+ self ._upload_client.start()
346+ self ._upload_client_started = True
347+
348+ return self ._upload_client
349+
350+ def _upload_file (self , name : str , path : str = None , content : bytes = None ) -> str :
351+ """ Upload file to execution server."""
352+ target_name = os.path.basename(name)
353+
354+ if path is not None :
355+ with open (path, " rb" ) as f:
356+ file_content = f.read()
357+ elif content is not None :
358+ file_content = content
359+ else :
360+ raise ValueError (" path or content must be provided" )
361+
362+ client = self ._get_upload_client()
363+ client.upload_file(target_name, file_content)
364+ return target_name
365+ ```
366+
367+ ### Security Considerations
368+
369+ 1 . ** Path Traversal Prevention** : Server sanitizes filename using ` os.path.basename() ` to prevent ` ../../etc/passwd ` attacks
370+ 2 . ** Session Isolation** : Files are written only to the session's own cwd directory
371+ 3 . ** API Key Authentication** : Upload endpoint respects the same API key auth as other endpoints
372+ 4 . ** Size Limits** : Large files should be chunked or streamed (not yet implemented)
373+
239374## Custom Kernel Magics (kernel/ext.py)
240375
241376``` python
@@ -271,6 +406,11 @@ Unit tests in `tests/unit_tests/ces/`:
271406| ` test_server_launcher.py ` | ServerLauncher (mocked subprocess/docker) |
272407| ` test_execution_service.py ` | ExecutionServiceProvider |
273408
409+ ** TODO** : Add tests for file upload functionality:
410+ - ` ExecutionClient.upload_file() ` - mock HTTP POST, verify base64 encoding
411+ - ` ServerSessionManager.upload_file() ` - verify file written, path traversal blocked
412+ - ` routes.upload_file() ` - integration test with mocked session manager
413+
274414Run tests:
275415``` bash
276416pytest tests/unit_tests/ces/ -v
0 commit comments