agentic-sigma-reveal 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,75 @@
1
+ Metadata-Version: 2.4
2
+ Name: agentic-sigma-reveal
3
+ Version: 0.2.0
4
+ Summary: Task-conditioned, budgeted initial workspace observation for CLI agent harnesses
5
+ Project-URL: Repository, https://github.com/Hoyant-Su/Agentic-Sigma-Reveal
6
+ Project-URL: Paper, https://arxiv.org/abs/2605.08013
7
+ Keywords: agent-harness,context-selection,cli-agent,sigma-reveal
8
+ Requires-Python: >=3.10
9
+ Description-Content-Type: text/markdown
10
+ Provides-Extra: mcp
11
+ Requires-Dist: mcp<3,>=2.2; extra == "mcp"
12
+ Provides-Extra: deepagents
13
+ Requires-Dist: deepagents<0.8,>=0.7.13; extra == "deepagents"
14
+
15
+ # Agentic Sigma-Reveal
16
+
17
+ Task-conditioned initial workspace observations for agent harnesses, with ancestor-closed tree selection under a character budget.
18
+
19
+ ```bash
20
+ pip install git+https://github.com/Hoyant-Su/Agentic-Sigma-Reveal.git
21
+ sigma-reveal ./workspace --task "Analyze sales.csv with Python" --budget-chars 2400
22
+ ```
23
+
24
+ ```python
25
+ from sigma_reveal import reveal
26
+ from sigma_reveal_reference import METHOD
27
+
28
+ context = reveal("./workspace", "Analyze sales.csv with Python", budget_chars=2400)
29
+ method_reference = METHOD
30
+ ```
31
+
32
+ The selector preserves the released method's scoring and tree optimization. Line costs are rounded up to 16-character buckets. The default budget is 2400 characters. Only the initial observation is generated; the receiving harness supplies subsequent observations.
33
+
34
+ ## Local MCP
35
+
36
+ ```bash
37
+ pip install 'agentic-sigma-reveal[mcp] @ git+https://github.com/Hoyant-Su/Agentic-Sigma-Reveal.git'
38
+ sigma-reveal-mcp --workspace ./workspace
39
+ ```
40
+
41
+ The stdio server exposes `select_workspace_context(task, budget_chars)` and `describe_method()`. Selection returns the workspace view and original method reference, including BibTeX. The `sigma-reveal://reference` resource returns the BibTeX alone. Client configuration after installation:
42
+
43
+ ```json
44
+ {
45
+ "mcpServers": {
46
+ "sigma-reveal": {
47
+ "command": "sigma-reveal-mcp",
48
+ "args": ["--workspace", "/path/to/task-workspace"]
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ The server binds to the workspace supplied at startup. Use a prepared workspace whose files may be read by the model. The MCP interface rejects symbolic links and special files in the inspected tree. Direct Python and CLI selection retain the original traversal behavior, including following symlinks. Keep the workspace unchanged during selection. File previews are untrusted task data.
55
+
56
+ The selector inspects up to four levels and reads up to 160 bytes per file for an 80-character first-line preview. The view budget constrains rendered output; it does not bound the number of directory entries inspected. Dynamic programming scales with tree size and the square of the budget in buckets.
57
+
58
+ ## Harness integration
59
+
60
+ `examples/deepagents_initial_context.py` supplies a static selected view to Deep Agents before its first action. See `examples/deepagents.md` for invocation and scope.
61
+
62
+ ## Method reference
63
+
64
+ The reference identifies the original observation method (section 3.2). The paper formulates a token-budget objective; its released implementation uses character buckets, as exposed by this package.
65
+
66
+ ```bibtex
67
+ @article{su2026structuredactioncredit,
68
+ title = {Learning CLI Agents with Structured Action Credit under Selective Observation},
69
+ author = {Su, Haoyang and Wen, Ying},
70
+ year = {2026},
71
+ journal = {arXiv preprint arXiv:2605.08013},
72
+ doi = {10.48550/arXiv.2605.08013},
73
+ url = {https://arxiv.org/abs/2605.08013}
74
+ }
75
+ ```
@@ -0,0 +1,61 @@
1
+ # Agentic Sigma-Reveal
2
+
3
+ Task-conditioned initial workspace observations for agent harnesses, with ancestor-closed tree selection under a character budget.
4
+
5
+ ```bash
6
+ pip install git+https://github.com/Hoyant-Su/Agentic-Sigma-Reveal.git
7
+ sigma-reveal ./workspace --task "Analyze sales.csv with Python" --budget-chars 2400
8
+ ```
9
+
10
+ ```python
11
+ from sigma_reveal import reveal
12
+ from sigma_reveal_reference import METHOD
13
+
14
+ context = reveal("./workspace", "Analyze sales.csv with Python", budget_chars=2400)
15
+ method_reference = METHOD
16
+ ```
17
+
18
+ The selector preserves the released method's scoring and tree optimization. Line costs are rounded up to 16-character buckets. The default budget is 2400 characters. Only the initial observation is generated; the receiving harness supplies subsequent observations.
19
+
20
+ ## Local MCP
21
+
22
+ ```bash
23
+ pip install 'agentic-sigma-reveal[mcp] @ git+https://github.com/Hoyant-Su/Agentic-Sigma-Reveal.git'
24
+ sigma-reveal-mcp --workspace ./workspace
25
+ ```
26
+
27
+ The stdio server exposes `select_workspace_context(task, budget_chars)` and `describe_method()`. Selection returns the workspace view and original method reference, including BibTeX. The `sigma-reveal://reference` resource returns the BibTeX alone. Client configuration after installation:
28
+
29
+ ```json
30
+ {
31
+ "mcpServers": {
32
+ "sigma-reveal": {
33
+ "command": "sigma-reveal-mcp",
34
+ "args": ["--workspace", "/path/to/task-workspace"]
35
+ }
36
+ }
37
+ }
38
+ ```
39
+
40
+ The server binds to the workspace supplied at startup. Use a prepared workspace whose files may be read by the model. The MCP interface rejects symbolic links and special files in the inspected tree. Direct Python and CLI selection retain the original traversal behavior, including following symlinks. Keep the workspace unchanged during selection. File previews are untrusted task data.
41
+
42
+ The selector inspects up to four levels and reads up to 160 bytes per file for an 80-character first-line preview. The view budget constrains rendered output; it does not bound the number of directory entries inspected. Dynamic programming scales with tree size and the square of the budget in buckets.
43
+
44
+ ## Harness integration
45
+
46
+ `examples/deepagents_initial_context.py` supplies a static selected view to Deep Agents before its first action. See `examples/deepagents.md` for invocation and scope.
47
+
48
+ ## Method reference
49
+
50
+ The reference identifies the original observation method (section 3.2). The paper formulates a token-budget objective; its released implementation uses character buckets, as exposed by this package.
51
+
52
+ ```bibtex
53
+ @article{su2026structuredactioncredit,
54
+ title = {Learning CLI Agents with Structured Action Credit under Selective Observation},
55
+ author = {Su, Haoyang and Wen, Ying},
56
+ year = {2026},
57
+ journal = {arXiv preprint arXiv:2605.08013},
58
+ doi = {10.48550/arXiv.2605.08013},
59
+ url = {https://arxiv.org/abs/2605.08013}
60
+ }
61
+ ```
@@ -0,0 +1,75 @@
1
+ Metadata-Version: 2.4
2
+ Name: agentic-sigma-reveal
3
+ Version: 0.2.0
4
+ Summary: Task-conditioned, budgeted initial workspace observation for CLI agent harnesses
5
+ Project-URL: Repository, https://github.com/Hoyant-Su/Agentic-Sigma-Reveal
6
+ Project-URL: Paper, https://arxiv.org/abs/2605.08013
7
+ Keywords: agent-harness,context-selection,cli-agent,sigma-reveal
8
+ Requires-Python: >=3.10
9
+ Description-Content-Type: text/markdown
10
+ Provides-Extra: mcp
11
+ Requires-Dist: mcp<3,>=2.2; extra == "mcp"
12
+ Provides-Extra: deepagents
13
+ Requires-Dist: deepagents<0.8,>=0.7.13; extra == "deepagents"
14
+
15
+ # Agentic Sigma-Reveal
16
+
17
+ Task-conditioned initial workspace observations for agent harnesses, with ancestor-closed tree selection under a character budget.
18
+
19
+ ```bash
20
+ pip install git+https://github.com/Hoyant-Su/Agentic-Sigma-Reveal.git
21
+ sigma-reveal ./workspace --task "Analyze sales.csv with Python" --budget-chars 2400
22
+ ```
23
+
24
+ ```python
25
+ from sigma_reveal import reveal
26
+ from sigma_reveal_reference import METHOD
27
+
28
+ context = reveal("./workspace", "Analyze sales.csv with Python", budget_chars=2400)
29
+ method_reference = METHOD
30
+ ```
31
+
32
+ The selector preserves the released method's scoring and tree optimization. Line costs are rounded up to 16-character buckets. The default budget is 2400 characters. Only the initial observation is generated; the receiving harness supplies subsequent observations.
33
+
34
+ ## Local MCP
35
+
36
+ ```bash
37
+ pip install 'agentic-sigma-reveal[mcp] @ git+https://github.com/Hoyant-Su/Agentic-Sigma-Reveal.git'
38
+ sigma-reveal-mcp --workspace ./workspace
39
+ ```
40
+
41
+ The stdio server exposes `select_workspace_context(task, budget_chars)` and `describe_method()`. Selection returns the workspace view and original method reference, including BibTeX. The `sigma-reveal://reference` resource returns the BibTeX alone. Client configuration after installation:
42
+
43
+ ```json
44
+ {
45
+ "mcpServers": {
46
+ "sigma-reveal": {
47
+ "command": "sigma-reveal-mcp",
48
+ "args": ["--workspace", "/path/to/task-workspace"]
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ The server binds to the workspace supplied at startup. Use a prepared workspace whose files may be read by the model. The MCP interface rejects symbolic links and special files in the inspected tree. Direct Python and CLI selection retain the original traversal behavior, including following symlinks. Keep the workspace unchanged during selection. File previews are untrusted task data.
55
+
56
+ The selector inspects up to four levels and reads up to 160 bytes per file for an 80-character first-line preview. The view budget constrains rendered output; it does not bound the number of directory entries inspected. Dynamic programming scales with tree size and the square of the budget in buckets.
57
+
58
+ ## Harness integration
59
+
60
+ `examples/deepagents_initial_context.py` supplies a static selected view to Deep Agents before its first action. See `examples/deepagents.md` for invocation and scope.
61
+
62
+ ## Method reference
63
+
64
+ The reference identifies the original observation method (section 3.2). The paper formulates a token-budget objective; its released implementation uses character buckets, as exposed by this package.
65
+
66
+ ```bibtex
67
+ @article{su2026structuredactioncredit,
68
+ title = {Learning CLI Agents with Structured Action Credit under Selective Observation},
69
+ author = {Su, Haoyang and Wen, Ying},
70
+ year = {2026},
71
+ journal = {arXiv preprint arXiv:2605.08013},
72
+ doi = {10.48550/arXiv.2605.08013},
73
+ url = {https://arxiv.org/abs/2605.08013}
74
+ }
75
+ ```
@@ -0,0 +1,13 @@
1
+ USAGE.md
2
+ pyproject.toml
3
+ sigma_reveal.py
4
+ sigma_reveal_mcp.py
5
+ sigma_reveal_reference.py
6
+ agentic_sigma_reveal.egg-info/PKG-INFO
7
+ agentic_sigma_reveal.egg-info/SOURCES.txt
8
+ agentic_sigma_reveal.egg-info/dependency_links.txt
9
+ agentic_sigma_reveal.egg-info/entry_points.txt
10
+ agentic_sigma_reveal.egg-info/requires.txt
11
+ agentic_sigma_reveal.egg-info/top_level.txt
12
+ tests/test_mcp.py
13
+ tests/test_selection.py
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ sigma-reveal = sigma_reveal:main
3
+ sigma-reveal-mcp = sigma_reveal_mcp:main
@@ -0,0 +1,6 @@
1
+
2
+ [deepagents]
3
+ deepagents<0.8,>=0.7.13
4
+
5
+ [mcp]
6
+ mcp<3,>=2.2
@@ -0,0 +1,3 @@
1
+ sigma_reveal
2
+ sigma_reveal_mcp
3
+ sigma_reveal_reference
@@ -0,0 +1,26 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "agentic-sigma-reveal"
7
+ version = "0.2.0"
8
+ description = "Task-conditioned, budgeted initial workspace observation for CLI agent harnesses"
9
+ requires-python = ">=3.10"
10
+ readme = "USAGE.md"
11
+ keywords = ["agent-harness", "context-selection", "cli-agent", "sigma-reveal"]
12
+
13
+ [project.optional-dependencies]
14
+ mcp = ["mcp>=2.2,<3"]
15
+ deepagents = ["deepagents>=0.7.13,<0.8"]
16
+
17
+ [project.scripts]
18
+ sigma-reveal = "sigma_reveal:main"
19
+ sigma-reveal-mcp = "sigma_reveal_mcp:main"
20
+
21
+ [project.urls]
22
+ Repository = "https://github.com/Hoyant-Su/Agentic-Sigma-Reveal"
23
+ Paper = "https://arxiv.org/abs/2605.08013"
24
+
25
+ [tool.setuptools]
26
+ py-modules = ["sigma_reveal", "sigma_reveal_reference", "sigma_reveal_mcp"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,248 @@
1
+ from __future__ import annotations
2
+ import argparse
3
+ import os
4
+ import re
5
+
6
+ _BUDGET_CHARS = 2400
7
+ _BUCKET_CHARS = 16
8
+ _MAX_DEPTH = 4
9
+ _MAX_HEAD_BYTES = 160
10
+ _LAMBDA_CITE = 4.0
11
+ _LAMBDA_DEPTH = 1.0
12
+ _LAMBDA_EXT = 0.8
13
+ _ALPHA_DEPTH = 0.7
14
+ _DIR_EXT_BASE = 0.3
15
+ _DATA_EXTS = {".csv", ".tsv", ".json", ".jsonl", ".txt", ".yaml", ".yml", ".log"}
16
+ _CODE_EXTS = {
17
+ ".py",
18
+ ".c",
19
+ ".cpp",
20
+ ".h",
21
+ ".hpp",
22
+ ".sh",
23
+ ".js",
24
+ ".ts",
25
+ ".go",
26
+ ".rs",
27
+ ".rb",
28
+ }
29
+ _DATA_CUES = ("csv", "tsv", "json", "yaml", "dataset", "data", "table", "row", "column")
30
+ _CODE_CUES = (
31
+ "script",
32
+ "function",
33
+ "class",
34
+ "module",
35
+ "python",
36
+ ".py",
37
+ "compile",
38
+ "run",
39
+ )
40
+ _TOKEN_RE = re.compile(r"[A-Za-z0-9_./-]+")
41
+
42
+ def _is_probably_text(data: bytes) -> bool:
43
+ if b"\x00" in data:
44
+ return False
45
+ printable = sum(1 for b in data if 9 <= b <= 13 or 32 <= b <= 126)
46
+ return printable / max(len(data), 1) >= 0.85
47
+
48
+ def _o0_token_set(text: str) -> set[str]:
49
+ out: set[str] = set()
50
+ for tok in _TOKEN_RE.findall(text):
51
+ t = tok.strip(".")
52
+ if not t:
53
+ continue
54
+ out.add(t)
55
+ out.add(t.lower())
56
+ stem = os.path.splitext(t)[0]
57
+ if stem and stem != t:
58
+ out.add(stem)
59
+ out.add(stem.lower())
60
+ return out
61
+
62
+ def _task_type_weights(text: str) -> tuple[float, float]:
63
+ low = text.lower()
64
+ data_hits = sum(1 for c in _DATA_CUES if c in low)
65
+ code_hits = sum(1 for c in _CODE_CUES if c in low)
66
+ total = data_hits + code_hits
67
+ if total == 0:
68
+ return 0.5, 0.5
69
+ return data_hits / total, code_hits / total
70
+
71
+ def _render_file_line(
72
+ name: str, size: int, is_text: bool, head: bytes, indent: str
73
+ ) -> str:
74
+ if not is_text:
75
+ return f"{indent}{name} ({size} B, binary)"
76
+ first_line = head.split(b"\n", 1)[0].decode("utf-8", "replace").strip()
77
+ if not first_line:
78
+ return f"{indent}{name} ({size} B, empty)"
79
+ if len(first_line) > 80:
80
+ first_line = first_line[:77] + "..."
81
+ return f"{indent}{name} ({size} B) -> {first_line!r}"
82
+
83
+ def _cite_score(name: str, o0_tokens: set[str]) -> float:
84
+ stem = os.path.splitext(name)[0]
85
+ if name in o0_tokens or name.lower() in o0_tokens:
86
+ return 1.0
87
+ if stem and (stem in o0_tokens or stem.lower() in o0_tokens):
88
+ return 1.0
89
+ return 0.0
90
+
91
+ def _ext_score(name: str, is_dir: bool, w_data: float, w_code: float) -> float:
92
+ if is_dir:
93
+ return _DIR_EXT_BASE
94
+ ext = os.path.splitext(name)[1].lower()
95
+ if ext in _DATA_EXTS:
96
+ return w_data
97
+ if ext in _CODE_EXTS:
98
+ return w_code
99
+ return 0.0
100
+
101
+ def _mu_hat(
102
+ name: str,
103
+ is_dir: bool,
104
+ depth: int,
105
+ o0_tokens: set[str],
106
+ w_data: float,
107
+ w_code: float,
108
+ ) -> float:
109
+ cite = _cite_score(name, o0_tokens)
110
+ depth_prior = _ALPHA_DEPTH**depth
111
+ ext = _ext_score(name, is_dir, w_data, w_code)
112
+ return _LAMBDA_CITE * cite + _LAMBDA_DEPTH * depth_prior + _LAMBDA_EXT * ext
113
+
114
+ class _Node:
115
+ __slots__ = ("idx", "name", "depth", "is_dir", "line", "cost_b", "mu", "children")
116
+
117
+ def __init__(
118
+ self,
119
+ idx: int,
120
+ name: str,
121
+ depth: int,
122
+ is_dir: bool,
123
+ line: str,
124
+ cost_b: int,
125
+ mu: float,
126
+ ) -> None:
127
+ self.idx = idx
128
+ self.name = name
129
+ self.depth = depth
130
+ self.is_dir = is_dir
131
+ self.line = line
132
+ self.cost_b = cost_b
133
+ self.mu = mu
134
+ self.children: list[_Node] = []
135
+
136
+ def _build_tree(
137
+ root_path: str,
138
+ o0_tokens: set[str],
139
+ w_data: float,
140
+ w_code: float,
141
+ ) -> _Node:
142
+ counter = [0]
143
+
144
+ def make_node(name: str, depth: int, is_dir: bool, line: str) -> _Node:
145
+ cost_b = max(1, (len(line) + 1 + _BUCKET_CHARS - 1) // _BUCKET_CHARS)
146
+ mu = _mu_hat(name, is_dir, depth, o0_tokens, w_data, w_code)
147
+ n = _Node(counter[0], name, depth, is_dir, line, cost_b, mu)
148
+ counter[0] += 1
149
+ return n
150
+
151
+ root = make_node(".", 0, True, ".")
152
+
153
+ def walk(path: str, parent: _Node, depth: int) -> None:
154
+ if depth > _MAX_DEPTH:
155
+ return
156
+ for child_name in sorted(os.listdir(path)):
157
+ child_path = os.path.join(path, child_name)
158
+ if os.path.isdir(child_path):
159
+ indent = " " * depth
160
+ line = f"{indent}{child_name}/"
161
+ node = make_node(child_name, depth, True, line)
162
+ parent.children.append(node)
163
+ walk(child_path, node, depth + 1)
164
+ else:
165
+ size = os.path.getsize(child_path)
166
+ with open(child_path, "rb") as fh:
167
+ head = fh.read(_MAX_HEAD_BYTES)
168
+ is_text = _is_probably_text(head)
169
+ indent = " " * depth
170
+ line = _render_file_line(child_name, size, is_text, head, indent)
171
+ parent.children.append(make_node(child_name, depth, False, line))
172
+
173
+ walk(root_path, root, 1)
174
+ return root
175
+
176
+ def _subtree_knapsack(root: _Node, budget_b: int) -> set[int]:
177
+ NEG = float("-inf")
178
+
179
+ def dfs(v: _Node) -> tuple[list[float], list[set[int]]]:
180
+ dp = [NEG] * (budget_b + 1)
181
+ pick: list[set[int]] = [set() for _ in range(budget_b + 1)]
182
+ if v.cost_b <= budget_b:
183
+ dp[v.cost_b] = v.mu
184
+ pick[v.cost_b] = {v.idx}
185
+ for child in v.children:
186
+ c_dp, c_pick = dfs(child)
187
+ new_dp = dp[:]
188
+ new_pick = [s.copy() for s in pick]
189
+ for bv in range(budget_b + 1):
190
+ if dp[bv] == NEG:
191
+ continue
192
+ remain = budget_b - bv
193
+ for bu in range(remain + 1):
194
+ if c_dp[bu] == NEG:
195
+ continue
196
+ val = dp[bv] + c_dp[bu]
197
+ tot = bv + bu
198
+ if val > new_dp[tot]:
199
+ new_dp[tot] = val
200
+ new_pick[tot] = pick[bv] | c_pick[bu]
201
+ dp = new_dp
202
+ pick = new_pick
203
+ return dp, pick
204
+
205
+ dp, pick = dfs(root)
206
+ best_b = 0
207
+ best_v = dp[0] if dp[0] != NEG else NEG
208
+ for b in range(budget_b + 1):
209
+ if dp[b] > best_v:
210
+ best_v = dp[b]
211
+ best_b = b
212
+ return pick[best_b]
213
+
214
+ def _render_selected(root: _Node, selected: set[int]) -> str:
215
+ lines: list[str] = []
216
+
217
+ def emit(node: _Node) -> None:
218
+ if node.idx not in selected:
219
+ return
220
+ lines.append(node.line)
221
+ for child in node.children:
222
+ emit(child)
223
+
224
+ emit(root)
225
+ return "\n".join(lines)
226
+
227
+ def reveal(root_path: str, task: str, budget_chars: int = _BUDGET_CHARS) -> str:
228
+ """Select an ancestor-closed initial workspace view using character buckets."""
229
+ if budget_chars < 0:
230
+ raise ValueError("budget_chars must be nonnegative")
231
+ tokens = _o0_token_set(task)
232
+ w_data, w_code = _task_type_weights(task)
233
+ tree = _build_tree(root_path, tokens, w_data, w_code)
234
+ selected = _subtree_knapsack(tree, budget_chars // _BUCKET_CHARS)
235
+ return _render_selected(tree, selected)
236
+
237
+
238
+ def main() -> None:
239
+ parser = argparse.ArgumentParser(description="Select a budgeted initial workspace observation.")
240
+ parser.add_argument("workspace")
241
+ parser.add_argument("--task", required=True)
242
+ parser.add_argument("--budget-chars", type=int, default=_BUDGET_CHARS)
243
+ args = parser.parse_args()
244
+ print(reveal(args.workspace, args.task, args.budget_chars))
245
+
246
+
247
+ if __name__ == "__main__":
248
+ main()
@@ -0,0 +1,57 @@
1
+ import argparse
2
+ import os
3
+ from pathlib import Path
4
+ from typing import Any
5
+
6
+ from mcp.server import MCPServer
7
+
8
+ from sigma_reveal import _MAX_DEPTH, reveal
9
+ from sigma_reveal_reference import BIBTEX, METHOD
10
+
11
+
12
+ def validate_workspace(root: Path) -> None:
13
+ if not root.is_dir():
14
+ raise ValueError('workspace must be an existing directory')
15
+ for directory, dirs, files in os.walk(root):
16
+ depth = len(Path(directory).relative_to(root).parts)
17
+ if depth >= _MAX_DEPTH:
18
+ dirs.clear()
19
+ continue
20
+ for name in dirs + files:
21
+ path = Path(directory) / name
22
+ if path.is_symlink() or not (path.is_dir() or path.is_file()):
23
+ raise ValueError('MCP workspace must contain only regular files and directories')
24
+
25
+
26
+ def create_server(workspace: Path) -> MCPServer:
27
+ root = workspace.resolve(strict=True)
28
+ server = MCPServer('Sigma-Reveal workspace context')
29
+
30
+ @server.tool(structured_output=True)
31
+ def select_workspace_context(task: str, budget_chars: int = 2400) -> dict[str, Any]:
32
+ """Select a static initial workspace view and return the method's original paper reference."""
33
+ validate_workspace(root)
34
+ return {'context': reveal(str(root), task, budget_chars), 'method': METHOD}
35
+
36
+ @server.tool(structured_output=True)
37
+ def describe_method() -> dict[str, Any]:
38
+ """Describe Sigma-Reveal's initial workspace selection method, scope, and original BibTeX."""
39
+ return METHOD
40
+
41
+ @server.resource('sigma-reveal://reference', mime_type='text/plain')
42
+ def reference() -> str:
43
+ """Original paper BibTeX for Sigma-Reveal workspace observation."""
44
+ return BIBTEX
45
+
46
+ return server
47
+
48
+
49
+ def main() -> None:
50
+ parser = argparse.ArgumentParser(description='Local Sigma-Reveal MCP server')
51
+ parser.add_argument('--workspace', type=Path, required=True)
52
+ args = parser.parse_args()
53
+ create_server(args.workspace).run()
54
+
55
+
56
+ if __name__ == '__main__':
57
+ main()
@@ -0,0 +1,14 @@
1
+ BIBTEX = '@article{su2026structuredactioncredit,\n title = {Learning CLI Agents with Structured Action Credit under Selective Observation},\n author = {Su, Haoyang and Wen, Ying},\n year = {2026},\n journal = {arXiv preprint arXiv:2605.08013},\n doi = {10.48550/arXiv.2605.08013},\n url = {https://arxiv.org/abs/2605.08013}\n}\n'
2
+
3
+ PAPER_URL = "https://arxiv.org/abs/2605.08013"
4
+
5
+ METHOD = {
6
+ "name": "Sigma-Reveal",
7
+ "purpose": "Task-conditioned initial workspace observation for CLI agent harnesses",
8
+ "selection": "Exact ancestor-closed tree knapsack with task-name, depth, and extension scores",
9
+ "budget_unit": "characters, rounded into 16-character line-cost buckets",
10
+ "scope": "Static initial observation before the first action; downstream effects require evaluation",
11
+ "paper_url": PAPER_URL,
12
+ "paper_section": "3.2",
13
+ "bibtex": BIBTEX,
14
+ }
@@ -0,0 +1,35 @@
1
+ import tempfile
2
+ import unittest
3
+ from pathlib import Path
4
+
5
+ from mcp import Client
6
+
7
+ from sigma_reveal import reveal
8
+ from sigma_reveal_mcp import create_server
9
+ from sigma_reveal_reference import BIBTEX
10
+
11
+
12
+ class MCPTests(unittest.IsolatedAsyncioTestCase):
13
+ async def test_selection_and_reference(self):
14
+ with tempfile.TemporaryDirectory() as directory:
15
+ root = Path(directory)
16
+ (root / 'sales.csv').write_text('year,revenue\n2025,12\n')
17
+ async with Client(create_server(root)) as client:
18
+ result = await client.call_tool('select_workspace_context', {'task': 'Analyze sales.csv', 'budget_chars': 80})
19
+ self.assertFalse(result.is_error)
20
+ self.assertEqual(result.structured_content['context'], reveal(directory, 'Analyze sales.csv', 80))
21
+ self.assertEqual(result.structured_content['method']['bibtex'], BIBTEX)
22
+ reference = await client.read_resource('sigma-reveal://reference')
23
+ self.assertEqual(reference.contents[0].text, BIBTEX)
24
+
25
+ async def test_rejects_symlink(self):
26
+ with tempfile.TemporaryDirectory() as directory:
27
+ root = Path(directory)
28
+ (root / 'link').symlink_to(root)
29
+ async with Client(create_server(root)) as client:
30
+ result = await client.call_tool('select_workspace_context', {'task': 'Inspect files'})
31
+ self.assertTrue(result.is_error)
32
+
33
+
34
+ if __name__ == '__main__':
35
+ unittest.main()
@@ -0,0 +1,44 @@
1
+ import itertools
2
+ import tempfile
3
+ import unittest
4
+ from pathlib import Path
5
+
6
+ from sigma_reveal import _Node, _subtree_knapsack, reveal
7
+
8
+
9
+ class SelectionTests(unittest.TestCase):
10
+ def test_optimality_and_ancestor_closure(self):
11
+ nodes = [_Node(i, str(i), 0, True, str(i), cost, score)
12
+ for i, (cost, score) in enumerate([(1, 1), (2, 3), (1, 5), (3, 4)])]
13
+ nodes[0].children = [nodes[1], nodes[3]]
14
+ nodes[1].children = [nodes[2]]
15
+ for budget in range(9):
16
+ selected = _subtree_knapsack(nodes[0], budget)
17
+ feasible_scores = [0]
18
+ for flags in itertools.product((False, True), repeat=4):
19
+ subset = {i for i, flag in enumerate(flags) if flag}
20
+ if subset and 0 not in subset:
21
+ continue
22
+ if 2 in subset and 1 not in subset:
23
+ continue
24
+ if sum(nodes[i].cost_b for i in subset) <= budget:
25
+ feasible_scores.append(sum(nodes[i].mu for i in subset))
26
+ self.assertLessEqual(sum(nodes[i].cost_b for i in selected), budget)
27
+ self.assertEqual(sum(nodes[i].mu for i in selected), max(feasible_scores))
28
+ self.assertTrue(2 not in selected or 1 in selected)
29
+
30
+ def test_rendered_budget_and_determinism(self):
31
+ with tempfile.TemporaryDirectory() as directory:
32
+ root = Path(directory)
33
+ (root / 'data').mkdir()
34
+ (root / 'data/sales.csv').write_text('year,revenue\n2025,12\n')
35
+ (root / 'run.py').write_text('print("hello")\n')
36
+ for budget in (0, 15, 16, 32, 80, 2400):
37
+ result = reveal(directory, 'Analyze sales.csv', budget)
38
+ self.assertLessEqual(len(result), budget)
39
+ self.assertEqual(result, reveal(directory, 'Analyze sales.csv', budget))
40
+ self.assertIn('sales.csv', reveal(directory, 'Analyze sales.csv'))
41
+
42
+
43
+ if __name__ == '__main__':
44
+ unittest.main()