11#!/usr/bin/env python3
22"""TurtleTerm local agent gateway v0.
33
4- This is the first executable scaffold for TurtleTerm's local agent gateway. It is
5- intentionally conservative: it supports local JSON requests over stdin/stdout or
6- a Unix socket, exposes read/inspect/propose flows, and records policy-required
7- execution requests without executing commands.
4+ Conservative local JSON gateway for TurtleTerm. It supports local requests over
5+ stdin/stdout or a Unix socket, exposes read/inspect/propose flows, and records
6+ policy-required execution requests without executing commands.
87"""
98
109from __future__ import annotations
@@ -21,7 +20,6 @@ from typing import Any
2120
2221
2322SCHEMA = "sourceos.turtle.agentd.response.v0"
24- REQUEST_SCHEMA = "sourceos.turtle.agentd.request.v0"
2523
2624
2725def utc_now () -> str :
@@ -64,7 +62,7 @@ def read_ndjson(path: Path, limit: int = 100) -> list[dict[str, Any]]:
6462
6563
6664def decision_stub (action : str , reason : str , decision : str = "ask" ) -> dict [str , Any ]:
67- payload = {
65+ return {
6866 "id" : f"urn:srcos:exec-decision:{ uuid .uuid4 ().hex } " ,
6967 "type" : "ExecutionDecision" ,
7068 "specVersion" : "2.0.0" ,
@@ -73,7 +71,6 @@ def decision_stub(action: str, reason: str, decision: str = "ask") -> dict[str,
7371 "issuedAt" : utc_now (),
7472 "action" : action ,
7573 }
76- return payload
7774
7875
7976def response (kind : str , data : dict [str , Any ] | list [Any ] | None = None , * , status : str = "ok" ) -> dict [str , Any ]:
@@ -86,6 +83,80 @@ def response(kind: str, data: dict[str, Any] | list[Any] | None = None, *, statu
8683 }
8784
8885
86+ def default_surfaces () -> list [dict [str , Any ]]:
87+ workspace = env ("SOURCEOS_WORKSPACE" , "default" )
88+ cwd = os .getcwd ()
89+ return [
90+ {
91+ "surface_id" : "local/default" ,
92+ "label" : "Local Workspace" ,
93+ "kind" : "host" ,
94+ "workspace_id" : workspace ,
95+ "cwd" : cwd ,
96+ "execution_domain" : "host" ,
97+ "policy_profile" : "standard" ,
98+ "agent_allowed" : False ,
99+ "receipt_required" : True ,
100+ "provider" : "turtleterm" ,
101+ },
102+ {
103+ "surface_id" : "tmux/attached" ,
104+ "label" : "Attached tmux Session" ,
105+ "kind" : "tmux" ,
106+ "workspace_id" : workspace ,
107+ "cwd" : cwd ,
108+ "execution_domain" : "host" ,
109+ "policy_profile" : "standard" ,
110+ "agent_allowed" : False ,
111+ "receipt_required" : True ,
112+ "provider" : "turtle-tmux" ,
113+ },
114+ {
115+ "surface_id" : "neovim/context" ,
116+ "label" : "Neovim Context" ,
117+ "kind" : "editor-context" ,
118+ "workspace_id" : workspace ,
119+ "cwd" : cwd ,
120+ "execution_domain" : "host" ,
121+ "policy_profile" : "review-only" ,
122+ "agent_allowed" : False ,
123+ "receipt_required" : True ,
124+ "provider" : "turtle.nvim" ,
125+ },
126+ {
127+ "surface_id" : "cloudfog/local-devshell" ,
128+ "label" : "CloudFog Local Devshell" ,
129+ "kind" : "devshell" ,
130+ "workspace_id" : workspace ,
131+ "cwd" : cwd ,
132+ "execution_domain" : "devshell" ,
133+ "policy_profile" : "low-risk" ,
134+ "agent_allowed" : False ,
135+ "receipt_required" : True ,
136+ "provider" : "cloudfog-shell" ,
137+ },
138+ {
139+ "surface_id" : "agent-machine/local-agentpod" ,
140+ "label" : "Agent Machine Local AgentPod" ,
141+ "kind" : "agentpod" ,
142+ "workspace_id" : workspace ,
143+ "cwd" : cwd ,
144+ "execution_domain" : "agent-machine" ,
145+ "policy_profile" : "activation-required" ,
146+ "agent_allowed" : False ,
147+ "receipt_required" : True ,
148+ "provider" : "agent-machine" ,
149+ },
150+ ]
151+
152+
153+ def surface_by_id (surface_id : str | None ) -> dict [str , Any ] | None :
154+ for surface in default_surfaces ():
155+ if surface ["surface_id" ] == surface_id :
156+ return surface
157+ return None
158+
159+
89160def handle_request (request : dict [str , Any ]) -> dict [str , Any ]:
90161 action = request .get ("action" )
91162
@@ -108,6 +179,31 @@ def handle_request(request: dict[str, Any]) -> dict[str, Any]:
108179 })
109180 return response ("sessions" , {"sessions" : list (sessions .values ()), "event_stream" : str (event_stream_path ())})
110181
182+ if action == "surfaces" :
183+ return response ("surfaces" , {"surfaces" : default_surfaces ()})
184+
185+ if action == "inspect_surface" :
186+ surface_id = request .get ("surface_id" )
187+ surface = surface_by_id (surface_id )
188+ if not surface :
189+ return response ("error" , {"message" : f"unknown surface: { surface_id } " , "known_surfaces" : [s ["surface_id" ] for s in default_surfaces ()]}, status = "error" )
190+ return response ("surface_inspection" , {"surface" : surface , "decision" : decision_stub ("surface.inspect" , "Surface inspection is review-only." , "allow" )})
191+
192+ if action == "request_surface_execution" :
193+ surface_id = request .get ("surface_id" )
194+ command = request .get ("command" )
195+ surface = surface_by_id (surface_id )
196+ if not surface :
197+ return response ("error" , {"message" : f"unknown surface: { surface_id } " }, status = "error" )
198+ if not command :
199+ return response ("error" , {"message" : "command is required" }, status = "error" )
200+ return response ("surface_execution_request" , {
201+ "surface" : surface ,
202+ "command" : command ,
203+ "executionAllowed" : False ,
204+ "decision" : decision_stub ("surface.execute_command" , "Surface execution requires Policy Fabric approval; turtle-agentd v0 does not execute commands." ),
205+ })
206+
111207 if action == "inspect" :
112208 session_id = request .get ("session_id" )
113209 events = [row for row in read_ndjson (event_stream_path (), limit = 500 ) if not session_id or row .get ("session_id" ) == session_id ]
@@ -148,6 +244,40 @@ def handle_request(request: dict[str, Any]) -> dict[str, Any]:
148244 "decision" : decision_stub ("terminal.execute_command" , "Execution requires Policy Fabric approval; turtle-agentd v0 does not execute commands." ),
149245 })
150246
247+ if action == "superconscious_observe" :
248+ return response ("superconscious_observation" , {
249+ "status" : "recorded" ,
250+ "observation" : request .get ("observation" , {}),
251+ "traceRef" : f"urn:srcos:turtle:superconscious-observation:{ uuid .uuid4 ().hex } " ,
252+ "decision" : decision_stub ("superconscious.observe" , "Observation handoff is review-only; no shell authority granted." , "allow" ),
253+ })
254+
255+ if action == "superconscious_propose" :
256+ return response ("superconscious_proposal" , {
257+ "status" : "deferred" ,
258+ "prompt" : request .get ("prompt" , "" ),
259+ "decision" : decision_stub ("superconscious.propose" , "Superconscious proposal requires cognition loop adapter; execution remains gated." , "defer" ),
260+ })
261+
262+ if action == "agent_machine_surfaces" :
263+ return response ("agent_machine_surfaces" , {"surfaces" : [s for s in default_surfaces () if s ["provider" ] == "agent-machine" ]})
264+
265+ if action == "agent_machine_probe" :
266+ return response ("agent_machine_probe" , {
267+ "status" : "deferred" ,
268+ "decision" : decision_stub ("agent-machine.probe" , "Agent Machine probe adapter is specified but not implemented in turtle-agentd v0." , "defer" ),
269+ })
270+
271+ if action == "cloudfog_surfaces" :
272+ return response ("cloudfog_surfaces" , {"surfaces" : [s for s in default_surfaces () if s ["provider" ] == "cloudfog-shell" ]})
273+
274+ if action == "cloudfog_inspect" :
275+ surface_id = request .get ("surface_id" , "cloudfog/local-devshell" )
276+ surface = surface_by_id (surface_id )
277+ if not surface or surface .get ("provider" ) != "cloudfog-shell" :
278+ return response ("error" , {"message" : f"unknown cloudfog surface: { surface_id } " }, status = "error" )
279+ return response ("cloudfog_surface_inspection" , {"surface" : surface , "decision" : decision_stub ("cloudfog.inspect" , "CloudFog surface inspection is review-only." , "allow" )})
280+
151281 if action == "receipts" :
152282 session_id = request .get ("session_id" )
153283 root = receipts_root ()
@@ -193,7 +323,7 @@ def serve_socket(path: Path) -> int:
193323 try :
194324 request = json .loads (raw .decode ("utf-8" ))
195325 result = handle_request (request )
196- except Exception as exc : # noqa: BLE001 - defensive boundary
326+ except Exception as exc : # noqa: BLE001
197327 result = response ("error" , {"message" : str (exc )}, status = "error" )
198328 conn .sendall ((json .dumps (result , sort_keys = True ) + "\n " ).encode ("utf-8" ))
199329 finally :
0 commit comments