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.
- agentic_sigma_reveal-0.2.0/PKG-INFO +75 -0
- agentic_sigma_reveal-0.2.0/USAGE.md +61 -0
- agentic_sigma_reveal-0.2.0/agentic_sigma_reveal.egg-info/PKG-INFO +75 -0
- agentic_sigma_reveal-0.2.0/agentic_sigma_reveal.egg-info/SOURCES.txt +13 -0
- agentic_sigma_reveal-0.2.0/agentic_sigma_reveal.egg-info/dependency_links.txt +1 -0
- agentic_sigma_reveal-0.2.0/agentic_sigma_reveal.egg-info/entry_points.txt +3 -0
- agentic_sigma_reveal-0.2.0/agentic_sigma_reveal.egg-info/requires.txt +6 -0
- agentic_sigma_reveal-0.2.0/agentic_sigma_reveal.egg-info/top_level.txt +3 -0
- agentic_sigma_reveal-0.2.0/pyproject.toml +26 -0
- agentic_sigma_reveal-0.2.0/setup.cfg +4 -0
- agentic_sigma_reveal-0.2.0/sigma_reveal.py +248 -0
- agentic_sigma_reveal-0.2.0/sigma_reveal_mcp.py +57 -0
- agentic_sigma_reveal-0.2.0/sigma_reveal_reference.py +14 -0
- agentic_sigma_reveal-0.2.0/tests/test_mcp.py +35 -0
- agentic_sigma_reveal-0.2.0/tests/test_selection.py +44 -0
|
@@ -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 @@
|
|
|
1
|
+
|
|
@@ -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,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()
|