Gate remote tool prompts by connector metadata
Hide A0 connector remote tool prompts unless a connected CLI advertises the matching capability. Remote file access enables text_editor_remote, F4-enabled remote execution enables code_execution_remote, and supported enabled Computer Use that is not in rearm-required state enables computer_use_remote. Apply the same gate to Responses API function-tool generation, move the prompt hook to the active tool-prompt extension path, and update connector prompt wording, DOX, and regression coverage. Verified with: - conda run -n a0 pytest tests/test_a0_connector_prompt_gating.py tests/test_default_prompt_budget.py tests/test_responses_architecture.py -q
Alessandro committed
Jun 13, 2026 at 02:00 UTC
2b99ab53c6ee6f3a0a9c9628ef0c71596274d2ba
11 files changed
+353
-60
helpers/responses_tools.py
+13
@@ -65,6 +65,8 @@ def _local_tool_prompts(agent: Any) -> list[tuple[str, str]]:
65
tool_name = _tool_name_from_prompt_basename(basename)
66
if not tool_name:
67
continue
68
+ if not _include_local_tool_prompt(agent, tool_name):
69
+ continue
70
try:
71
prompt = agent.read_prompt(basename)
72
except Exception:
@@ -76,6 +78,17 @@ def _local_tool_prompts(agent: Any) -> list[tuple[str, str]]:
78
return result
79
80
81
+def _include_local_tool_prompt(agent: Any, tool_name: str) -> bool:
82
+ try:
83
+ from plugins._a0_connector.helpers.remote_tool_prompts import (
84
+ should_include_remote_tool_prompt,
85
+ )
86
+ except Exception:
87
+ return True
88
+
89
+ return should_include_remote_tool_prompt(agent, tool_name)
90
+
91
+
92
def _mcp_tools(agent: Any) -> list[tuple[str, dict[str, Any]]]:
93
try:
94
import helpers.mcp_handler as mcp_helper
helpers/responses_tools.py.dox.md
new
+28
@@ -0,0 +1,28 @@
1
+# responses_tools.py DOX
2
+
3
+## Purpose
4
+
5
+- Own conversion of Agent Zero tool prompt files and MCP tool metadata into OpenAI Responses API function tool definitions.
6
+- Keep native Responses function availability synchronized with the text tool prompt surface.
7
+
8
+## Ownership
9
+
10
+- `responses_tools.py` owns runtime implementation.
11
+- `responses_tools.py.dox.md` owns durable notes about responsibilities, prompt-derived contracts, and verification for this helper.
12
+
13
+## Local Contracts
14
+
15
+- Build local function tools from enabled `agent.system.tool.*.md` prompt files.
16
+- Preserve original Agent Zero tool names through the native Responses name map.
17
+- Keep MCP tool schemas merged after local prompt-derived tools.
18
+- Connector remote tools are advertised only when `_a0_connector` runtime metadata says the matching connected CLI capability is currently available.
19
+
20
+## Work Guidance
21
+
22
+- Keep prompt-derived descriptions bounded by `MAX_TOOL_DESCRIPTION_CHARS`.
23
+- Treat plugin-specific tool gates as optional imports so core helper loading does not require a plugin that is absent or disabled.
24
+
25
+## Verification
26
+
27
+- Run targeted Responses/tool prompt tests after changing function-tool construction.
28
+- Run connector prompt gating tests when changing remote tool availability.
plugins/_a0_connector/AGENTS.md
+5
-1
@@ -15,7 +15,11 @@
15
## Local Contracts
16
17
- Preserve session-auth and `auth.handlers` activation assumptions.
18
-- Keep remote tool prompts synchronized with remote tool behavior.
18
+- Keep remote tool prompts synchronized with remote tool behavior and disclose
19
+ them only from connected CLI metadata: no connected CLI hides all remote tool
20
+ prompts, remote file metadata enables `text_editor_remote`, F4-enabled remote
21
+ execution metadata enables `code_execution_remote`, and supported enabled
22
+ Computer Use that does not need re-arming enables `computer_use_remote`.
23
- Do not bypass WebSocket authentication or leak connector session data.
24
25
## Work Guidance
plugins/_a0_connector/extensions/python/_functions/_11_tools_prompt/build_prompt/end/_70_include_remote_tool_stubs.py
new
+66
@@ -0,0 +1,66 @@
1
+from __future__ import annotations
2
+
3
+import re
4
+from typing import Any
5
+
6
+from helpers.extension import Extension
7
+from plugins._a0_connector.helpers.remote_tool_prompts import (
8
+ REMOTE_TOOL_PROMPTS,
9
+ remote_tool_prompt_availability,
10
+)
11
+
12
+
13
+_TOOL_MARKERS = {
14
+ tool_name: f'"tool_name": "{tool_name}"' for tool_name in REMOTE_TOOL_PROMPTS
15
+}
16
+
17
+
18
+class IncludeRemoteToolStubs(Extension):
19
+ def execute(self, data: dict[str, Any] = {}, **kwargs: Any) -> None:
20
+ if self.agent is None:
21
+ return
22
+ if not isinstance(data, dict):
23
+ return
24
+ result = data.get("result")
25
+ if not isinstance(result, str):
26
+ return
27
+
28
+ context_id = str(
29
+ getattr(getattr(self.agent, "context", None), "id", "") or ""
30
+ ).strip()
31
+ if not context_id:
32
+ return
33
+
34
+ available = remote_tool_prompt_availability(context_id)
35
+ for tool_name, prompt_file in REMOTE_TOOL_PROMPTS.items():
36
+ try:
37
+ prompt = self.agent.read_prompt(prompt_file).strip()
38
+ except Exception:
39
+ continue
40
+ if not prompt:
41
+ continue
42
+
43
+ if available.get(tool_name):
44
+ marker = _TOOL_MARKERS[tool_name]
45
+ if marker not in result:
46
+ result = f"{result.rstrip()}\n\n{prompt}"
47
+ continue
48
+
49
+ result = _remove_prompt(result, prompt)
50
+
51
+ data["result"] = result
52
+
53
+
54
+def _remove_prompt(result: str, prompt: str) -> str:
55
+ if prompt not in result:
56
+ return result
57
+
58
+ for needle, replacement in (
59
+ (f"\n\n{prompt}\n\n", "\n\n"),
60
+ (f"\n\n{prompt}", ""),
61
+ (f"{prompt}\n\n", ""),
62
+ (prompt, ""),
63
+ ):
64
+ result = result.replace(needle, replacement)
65
+
66
+ return re.sub(r"\n{3,}", "\n\n", result).rstrip()
plugins/_a0_connector/extensions/python/_functions/extensions/python/system_prompt/_11_tools_prompt/build_prompt/end/_70_include_remote_tool_stubs.py
deleted
-31
@@ -1,31 +0,0 @@
1
-from __future__ import annotations
2
-
3
-from typing import Any
4
-
5
-from helpers.extension import Extension
6
-
7
-
8
-_COMPUTER_USE_PROMPT = "agent.system.tool.computer_use_remote.md"
9
-_COMPUTER_USE_TOOL_MARKER = '"tool_name": "computer_use_remote"'
10
-
11
-
12
-class IncludeRemoteToolStubs(Extension):
13
- def execute(self, data: dict[str, Any] = {}, **kwargs: Any) -> None:
14
- if self.agent is None:
15
- return
16
- if not isinstance(data, dict):
17
- return
18
- result = data.get("result")
19
- if not isinstance(result, str):
20
- return
21
- if _COMPUTER_USE_TOOL_MARKER in result:
22
- return
23
-
24
- try:
25
- prompt = self.agent.read_prompt(_COMPUTER_USE_PROMPT).strip()
26
- except Exception:
27
- return
28
- if not prompt:
29
- return
30
-
31
- data["result"] = f"{result.rstrip()}\n\n{prompt}"
plugins/_a0_connector/helpers/remote_tool_prompts.py
new
+60
@@ -0,0 +1,60 @@
1
+from __future__ import annotations
2
+
3
+from typing import Any
4
+
5
+from plugins._a0_connector.helpers import ws_runtime
6
+
7
+
8
+REMOTE_TOOL_PROMPTS: dict[str, str] = {
9
+ "code_execution_remote": "agent.system.tool.code_execution_remote.md",
10
+ "computer_use_remote": "agent.system.tool.computer_use_remote.md",
11
+ "text_editor_remote": "agent.system.tool.text_editor_remote.md",
12
+}
13
+
14
+
15
+def remote_tool_prompt_availability(context_id: str) -> dict[str, bool]:
16
+ """Return which connector remote tools should be advertised in prompts."""
17
+ candidates = ws_runtime.remote_tool_sids_for_context(context_id)
18
+ return {
19
+ "code_execution_remote": any(
20
+ _remote_exec_prompt_available(sid) for sid in candidates
21
+ ),
22
+ "computer_use_remote": any(
23
+ _computer_use_prompt_available(sid) for sid in candidates
24
+ ),
25
+ "text_editor_remote": any(
26
+ _remote_file_prompt_available(sid) for sid in candidates
27
+ ),
28
+ }
29
+
30
+
31
+def should_include_remote_tool_prompt(agent: Any, tool_name: str) -> bool:
32
+ if tool_name not in REMOTE_TOOL_PROMPTS:
33
+ return True
34
+
35
+ context = getattr(agent, "context", None)
36
+ context_id = str(getattr(context, "id", "") or "").strip()
37
+ if not context_id:
38
+ return False
39
+
40
+ return bool(remote_tool_prompt_availability(context_id).get(tool_name))
41
+
42
+
43
+def _remote_file_prompt_available(sid: str) -> bool:
44
+ metadata = ws_runtime.remote_file_metadata_for_sid(sid) or {}
45
+ return bool(metadata.get("enabled"))
46
+
47
+
48
+def _remote_exec_prompt_available(sid: str) -> bool:
49
+ metadata = ws_runtime.remote_exec_metadata_for_sid(sid) or {}
50
+ return bool(metadata.get("enabled"))
51
+
52
+
53
+def _computer_use_prompt_available(sid: str) -> bool:
54
+ metadata = ws_runtime.computer_use_metadata_for_sid(sid) or {}
55
+ status = str(metadata.get("status", "") or "").strip().lower()
56
+ return bool(
57
+ metadata.get("supported")
58
+ and metadata.get("enabled")
59
+ and status != "rearm required"
60
+ )
plugins/_a0_connector/prompts/agent.system.tool.code_execution_remote.md
+5
-4
@@ -1,9 +1,10 @@
1
# code_execution_remote tool
2
3
-Runs shell-backed execution on the machine where a connected A0 CLI is running.
4
-Use this tool, not `code_execution_tool`, when the user asks for the connected
5
-local terminal, the A0 CLI host, their local machine, or explicitly says not to
6
-use Docker/server/container execution.
3
+Shown when a connected A0 CLI advertises remote execution, which the user enables
4
+with F4 in the CLI. Runs shell-backed execution on the machine where that CLI is
5
+running. Use this tool, not `code_execution_tool`, when the user asks for the
6
+connected local terminal, the A0 CLI host, their local machine, or explicitly
7
+says not to use Docker/server/container execution.
8
For complex local project work, optionally load skill `host-code-execution`.
9
10
Availability and permissions are checked when the tool runs. If no CLI is
plugins/_a0_connector/prompts/agent.system.tool.computer_use_remote.md
+1
-1
@@ -1,6 +1,6 @@
1
### computer_use_remote
2
3
-Runtime-gated beta desktop control through a connected A0 CLI on the user's host machine. The callable contract is available in the tool prompt. Availability, backend support, and trust mode are checked when the tool runs, together with CLI presence, local enablement, and re-arm state. Computer Use enablement is scoped to the current CLI session, not scoped to a single chat context.
3
+Shown when a connected A0 CLI advertises enabled Computer Use (`/computer-use on`) and does not need re-arming. Runtime-gated beta desktop control through that CLI on the user's host machine. Availability, backend support, and trust mode are checked when the tool runs, together with CLI presence, local enablement, and re-arm state. Computer Use enablement is scoped to the current CLI session, not scoped to a single chat context.
4
5
Use this for native host desktop UI inspection, screenshots, background-safe window/element actions when supported, clicking, scrolling, typing, key presses, and status checks. Do not use it for ordinary web-page navigation or host-browser control; use the browser tool for web pages unless browser automation cannot express the task. For complex desktop workflows, load and follow skill `host-computer-use` before proceeding.
6
plugins/_a0_connector/prompts/agent.system.tool.text_editor_remote.md
+5
-4
@@ -1,9 +1,10 @@
1
# text_editor_remote tool
2
3
-Reads, writes, and patches files on the machine where a connected A0 CLI is
4
-running. Use this tool, not server-side file tools, when the user asks for files
5
-on the connected local machine, A0 CLI host, or explicitly says not to use
6
-Docker/server files. For complex remote edits, optionally load skill `host-file-editing`.
3
+Shown when a connected A0 CLI advertises remote file access. Reads, writes, and
4
+patches files on the machine where that CLI is running. Use this tool, not
5
+server-side file tools, when the user asks for files on the connected local
6
+machine, A0 CLI host, or explicitly says not to use Docker/server files. For
7
+complex remote edits, optionally load skill `host-file-editing`.
8
9
Availability and permissions are checked when the tool runs. If no CLI is
10
connected, remote file access is disabled, or a write/patch needs Read&Write,
tests/test_a0_connector_prompt_gating.py
+162
-13
@@ -27,6 +27,11 @@ from plugins._a0_connector.helpers import ws_runtime
27
28
29
PROMPT_ROOT = PROJECT_ROOT / "plugins" / "_a0_connector" / "prompts"
30
+REMOTE_PROMPT_FILES = {
31
+ "code_execution_remote": "agent.system.tool.code_execution_remote.md",
32
+ "computer_use_remote": "agent.system.tool.computer_use_remote.md",
33
+ "text_editor_remote": "agent.system.tool.text_editor_remote.md",
34
+}
35
GATE_PATH = (
36
PROJECT_ROOT
37
/ "plugins"
@@ -34,9 +39,6 @@ GATE_PATH = (
39
/ "extensions"
40
/ "python"
41
/ "_functions"
37
- / "extensions"
38
- / "python"
39
- / "system_prompt"
42
/ "_11_tools_prompt"
43
/ "build_prompt"
44
/ "end"
@@ -89,24 +91,167 @@ def _parse_skill_frontmatter(path: Path) -> dict:
91
return yaml.safe_load(text.split("---", 2)[1]) or {}
92
93
92
-def _apply_gate(context_id: str) -> str:
93
- data = {"result": "## available tools\nbase_tool"}
94
+def _remote_prompt_blob() -> str:
95
+ return "\n\n".join(
96
+ (PROMPT_ROOT / prompt_file).read_text(encoding="utf-8").strip()
97
+ for prompt_file in REMOTE_PROMPT_FILES.values()
98
+ )
99
+
100
+
101
+def _apply_gate(context_id: str, *, include_standard_remote_prompts: bool = True) -> str:
102
+ result = "## available tools\nbase_tool"
103
+ if include_standard_remote_prompts:
104
+ result = f"{result}\n\n{_remote_prompt_blob()}"
105
+ data = {"result": result}
106
IncludeRemoteToolStubs(agent=FakeAgent(context_id)).execute(data=data)
107
return data["result"]
108
109
98
-def test_remote_tool_gate_includes_runtime_checked_computer_use_contract():
110
+def _assert_remote_tool_absent(prompt: str, tool_name: str) -> None:
111
+ assert f'"tool_name": "{tool_name}"' not in prompt
112
+
113
+
114
+def test_remote_tool_gate_hides_remote_prompts_without_connected_cli():
115
prompt = _apply_gate(_context_id())
116
101
- assert "text_editor_remote tool" not in prompt
102
- assert "code_execution_remote tool" not in prompt
117
+ for tool_name in REMOTE_PROMPT_FILES:
118
+ _assert_remote_tool_absent(prompt, tool_name)
119
+ assert "base_tool" in prompt
120
+
121
+
122
+def test_remote_tool_gate_includes_file_prompt_for_read_only_connected_cli():
123
+ context_id = _context_id()
124
+ sid = _sid()
125
+ ws_runtime.register_sid(sid)
126
+ ws_runtime.store_sid_remote_file_metadata(
127
+ sid,
128
+ {"enabled": True, "write_enabled": False, "mode": "read_only"},
129
+ )
130
+ try:
131
+ prompt = _apply_gate(context_id)
132
+ finally:
133
+ ws_runtime.unregister_sid(sid)
134
+
135
+ assert '"tool_name": "text_editor_remote"' in prompt
136
+ _assert_remote_tool_absent(prompt, "code_execution_remote")
137
+ _assert_remote_tool_absent(prompt, "computer_use_remote")
138
+
139
+
140
+def test_remote_tool_gate_requires_f4_enabled_remote_exec_metadata():
141
+ context_id = _context_id()
142
+ sid = _sid()
143
+ ws_runtime.register_sid(sid)
144
+ ws_runtime.store_sid_remote_file_metadata(
145
+ sid,
146
+ {"enabled": True, "write_enabled": True, "mode": "read_write"},
147
+ )
148
+ ws_runtime.store_sid_remote_exec_metadata(sid, {"enabled": False})
149
+ try:
150
+ prompt = _apply_gate(context_id)
151
+ _assert_remote_tool_absent(prompt, "code_execution_remote")
152
+
153
+ ws_runtime.store_sid_remote_exec_metadata(sid, {"enabled": True})
154
+ prompt = _apply_gate(context_id)
155
+ finally:
156
+ ws_runtime.unregister_sid(sid)
157
+
158
+ assert '"tool_name": "code_execution_remote"' in prompt
159
+
160
+
161
+def test_remote_tool_gate_requires_enabled_computer_use_metadata():
162
+ context_id = _context_id()
163
+ sid = _sid()
164
+ ws_runtime.register_sid(sid)
165
+ ws_runtime.store_sid_computer_use_metadata(
166
+ sid,
167
+ {"supported": True, "enabled": False, "status": "off"},
168
+ )
169
+ try:
170
+ prompt = _apply_gate(context_id)
171
+ _assert_remote_tool_absent(prompt, "computer_use_remote")
172
+
173
+ ws_runtime.store_sid_computer_use_metadata(
174
+ sid,
175
+ {"supported": True, "enabled": True, "status": "ready"},
176
+ )
177
+ prompt = _apply_gate(context_id)
178
+ finally:
179
+ ws_runtime.unregister_sid(sid)
180
+
181
assert '"tool_name": "computer_use_remote"' in prompt
182
assert "### computer_use_remote" in prompt
105
- assert "checked when the tool runs" in prompt
183
+
184
+
185
+def test_remote_tool_gate_hides_rearm_required_computer_use_prompt():
186
+ context_id = _context_id()
187
+ sid = _sid()
188
+ ws_runtime.register_sid(sid)
189
+ ws_runtime.store_sid_computer_use_metadata(
190
+ sid,
191
+ {
192
+ "supported": True,
193
+ "enabled": True,
194
+ "status": "rearm required",
195
+ "last_error": "permission expired",
196
+ },
197
+ )
198
+ try:
199
+ prompt = _apply_gate(context_id)
200
+ finally:
201
+ ws_runtime.unregister_sid(sid)
202
+
203
+ _assert_remote_tool_absent(prompt, "computer_use_remote")
204
+
205
+
206
+def test_remote_tool_gate_appends_available_prompt_when_standard_prompt_missing():
207
+ context_id = _context_id()
208
+ sid = _sid()
209
+ ws_runtime.register_sid(sid)
210
+ ws_runtime.store_sid_remote_file_metadata(sid, {"enabled": True})
211
+ try:
212
+ prompt = _apply_gate(context_id, include_standard_remote_prompts=False)
213
+ finally:
214
+ ws_runtime.unregister_sid(sid)
215
+
216
+ assert '"tool_name": "text_editor_remote"' in prompt
217
+ _assert_remote_tool_absent(prompt, "code_execution_remote")
218
+ _assert_remote_tool_absent(prompt, "computer_use_remote")
219
+
220
+
221
+def test_responses_function_tools_follow_remote_prompt_gate(monkeypatch):
222
+ from helpers import responses_tools
223
+
224
+ context_id = _context_id()
225
+ agent = FakeAgent(context_id)
226
+ monkeypatch.setattr(
227
+ responses_tools.subagents,
228
+ "get_paths",
229
+ lambda *args, **kwargs: [str(PROMPT_ROOT)],
230
+ )
231
+
232
+ names = {name for name, _prompt in responses_tools._local_tool_prompts(agent)}
233
+ assert names.isdisjoint(REMOTE_PROMPT_FILES)
234
+
235
+ sid = _sid()
236
+ ws_runtime.register_sid(sid)
237
+ ws_runtime.store_sid_remote_file_metadata(sid, {"enabled": True})
238
+ ws_runtime.store_sid_remote_exec_metadata(sid, {"enabled": True})
239
+ ws_runtime.store_sid_computer_use_metadata(
240
+ sid,
241
+ {"supported": True, "enabled": True, "status": "ready"},
242
+ )
243
+ try:
244
+ names = {name for name, _prompt in responses_tools._local_tool_prompts(agent)}
245
+ finally:
246
+ ws_runtime.unregister_sid(sid)
247
+
248
+ assert REMOTE_PROMPT_FILES.keys() <= names
249
250
251
def test_computer_use_remote_prompt_is_cli_session_wide_not_context_scoped():
109
- prompt = _apply_gate(_context_id())
252
+ prompt = (PROMPT_ROOT / "agent.system.tool.computer_use_remote.md").read_text(
253
+ encoding="utf-8"
254
+ )
255
256
assert "### computer_use_remote" in prompt
257
assert '"tool_name": "computer_use_remote"' in prompt
@@ -115,7 +260,9 @@ def test_computer_use_remote_prompt_is_cli_session_wide_not_context_scoped():
260
261
262
def test_computer_use_remote_prompt_keeps_runtime_failures_actionable():
118
- prompt = _apply_gate(_context_id())
263
+ prompt = (PROMPT_ROOT / "agent.system.tool.computer_use_remote.md").read_text(
264
+ encoding="utf-8"
265
+ )
266
267
assert "no CLI" in prompt
268
assert "disabled computer use" in prompt
@@ -124,7 +271,9 @@ def test_computer_use_remote_prompt_keeps_runtime_failures_actionable():
271
272
273
def test_computer_use_remote_prompt_requires_visual_verification_after_actions():
127
- prompt = _apply_gate(_context_id())
274
+ prompt = (PROMPT_ROOT / "agent.system.tool.computer_use_remote.md").read_text(
275
+ encoding="utf-8"
276
+ )
277
skill = (
278
PROJECT_ROOT
279
/ "plugins"
@@ -152,7 +301,7 @@ def test_computer_use_remote_prompt_requires_visual_verification_after_actions()
301
assert "window-manager" not in skill
302
303
155
-def test_remote_file_and_exec_tools_are_standard_tool_prompts_independent_from_context():
304
+def test_remote_file_and_exec_tool_prompt_files_remain_standard_tool_prompts():
305
text_stub = (PROMPT_ROOT / "agent.system.tool.text_editor_remote.md").read_text(encoding="utf-8")
306
exec_stub = (PROMPT_ROOT / "agent.system.tool.code_execution_remote.md").read_text(encoding="utf-8")
307
tests/test_default_prompt_budget.py
+8
-6
@@ -50,7 +50,7 @@ async def test_default_agent0_prompt_budget_and_guardrails():
50
# surface plus skill metadata. Keep the guardrail close to the observed
51
# budget so prompt creep remains visible without pretending this surface is
52
# a tiny single-tool prompt.
53
- assert tokens.approximate_tokens(system_text) <= 12000
53
+ assert tokens.approximate_tokens(system_text) <= 10000
54
assert "`tool_name` must be one listed tool name" in system_text
55
assert "- tool_args: key value pairs tool arguments" in system_text
56
assert '"tool_name": "call_subordinate"' in system_text
@@ -62,11 +62,13 @@ async def test_default_agent0_prompt_budget_and_guardrails():
62
assert '"tool_name": "code_execution_tool"' in system_text
63
assert '"tool_name": "memory_load"' in system_text
64
assert "informative but tight" in system_text
65
- assert '"tool_name": "code_execution_remote"' in system_text
66
- assert '"tool_name": "text_editor_remote"' in system_text
67
- assert '"tool_name": "computer_use_remote"' in system_text
68
- assert "Computer Use enablement is scoped to the current CLI session" in system_text
69
- assert "host-computer-use" in system_text
65
+ assert "# code_execution_remote tool" not in system_text
66
+ assert "# text_editor_remote tool" not in system_text
67
+ assert "### computer_use_remote" not in system_text
68
+ assert '"tool_name": "code_execution_remote"' not in system_text
69
+ assert '"tool_name": "text_editor_remote"' not in system_text
70
+ assert '"tool_name": "computer_use_remote"' not in system_text
71
+ assert "Computer Use enablement is scoped to the current CLI session" not in system_text
72
73
74
def test_removed_small_profile_and_prompt_text_generic():