align-agent 0.4.3__py3-none-any.whl

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.
Files changed (60) hide show
  1. align_agent/__init__.py +11 -0
  2. align_agent/_audit/common.py +58 -0
  3. align_agent/_audit/deepswe.py +260 -0
  4. align_agent/_audit/excerpts.py +203 -0
  5. align_agent/_audit/extract.py +798 -0
  6. align_agent/_audit/materialize.py +624 -0
  7. align_agent/_audit/score.py +325 -0
  8. align_agent/_audit/shellparse.py +1141 -0
  9. align_agent/_audit/tarsur.py +153 -0
  10. align_agent/_vendor/src/__init__.py +3 -0
  11. align_agent/_vendor/src/harness/__init__.py +13 -0
  12. align_agent/_vendor/src/harness/arms.py +524 -0
  13. align_agent/_vendor/src/harness/completion.py +104 -0
  14. align_agent/_vendor/src/harness/dependency_notice.py +616 -0
  15. align_agent/_vendor/src/harness/freshness_hook.py +131 -0
  16. align_agent/_vendor/src/harness/lean.py +41 -0
  17. align_agent/_vendor/src/harness/mini_real.py +1040 -0
  18. align_agent/_vendor/src/harness/miniswe_scaffold.py +661 -0
  19. align_agent/_vendor/src/harness/tail_diff.py +292 -0
  20. align_agent/_vendor/src/harness/viewport.py +220 -0
  21. align_agent/_vendor/src/metrics/__init__.py +38 -0
  22. align_agent/_vendor/src/metrics/coherence.py +354 -0
  23. align_agent/_vendor/src/metrics/corruption_oracles.py +139 -0
  24. align_agent/_vendor/src/metrics/replay.py +399 -0
  25. align_agent/_vendor/src/metrics/statistics.py +185 -0
  26. align_agent/_vendor/src/metrics/verification.py +104 -0
  27. align_agent/_vendor/src/parsers/__init__.py +33 -0
  28. align_agent/_vendor/src/parsers/base_parser.py +349 -0
  29. align_agent/_vendor/src/parsers/file_events.py +806 -0
  30. align_agent/_vendor/src/parsers/miniswe_parser.py +96 -0
  31. align_agent/_vendor/src/parsers/openhands_parser.py +414 -0
  32. align_agent/_vendor/src/parsers/sweagent_parser.py +180 -0
  33. align_agent/_vendor/src/parsers/terminus_parser.py +106 -0
  34. align_agent/cli.py +101 -0
  35. align_agent/core.py +161 -0
  36. align_agent/run.py +84 -0
  37. align_agent/sessions.py +985 -0
  38. align_agent/writethrough/__init__.py +4 -0
  39. align_agent/writethrough/_lib/__init__.py +1 -0
  40. align_agent/writethrough/_lib/wt_src/__init__.py +3 -0
  41. align_agent/writethrough/_lib/wt_src/harness/__init__.py +1 -0
  42. align_agent/writethrough/_lib/wt_src/harness/dependency_notice.py +616 -0
  43. align_agent/writethrough/_lib/wt_src/harness/lean.py +41 -0
  44. align_agent/writethrough/_lib/wt_src/harness/tail_diff.py +292 -0
  45. align_agent/writethrough/_lib/wt_src/harness/viewport.py +220 -0
  46. align_agent/writethrough/_lib/wt_src/metrics/__init__.py +38 -0
  47. align_agent/writethrough/_lib/wt_src/metrics/replay.py +399 -0
  48. align_agent/writethrough/_lib/wt_src/parsers/__init__.py +1 -0
  49. align_agent/writethrough/_lib/wt_src/parsers/file_events.py +806 -0
  50. align_agent/writethrough/claude_code.py +81 -0
  51. align_agent/writethrough/engine.py +647 -0
  52. align_agent/writethrough/hooks.py +227 -0
  53. align_agent/writethrough/mcp_server.py +125 -0
  54. align_agent/writethrough/protocol.py +68 -0
  55. align_agent-0.4.3.dist-info/METADATA +224 -0
  56. align_agent-0.4.3.dist-info/RECORD +60 -0
  57. align_agent-0.4.3.dist-info/WHEEL +5 -0
  58. align_agent-0.4.3.dist-info/entry_points.txt +6 -0
  59. align_agent-0.4.3.dist-info/licenses/LICENSE +21 -0
  60. align_agent-0.4.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,153 @@
1
+ """Recount on tarsur385/swebench-verified-trajectories (mini-swe-agent tool-call format). Usage: python3 scripts/audit/tarsur.py
2
+
3
+ Adapter: each assistant message's tool_calls (function 'bash', arguments.command)
4
+ is one step; its reply is the role:'tool' message with the same
5
+ tool_call_id ('Exit code: N' header gives the return code). Commands are
6
+ parsed with scripts/audit/shellparse.py, outcomes with extract.shell_outcome,
7
+ scored with score.Replay and classified with materialize.classify (automated
8
+ classifier only; no human labels exist for this corpus). Reports event
9
+ coverage first (steps with a tool call, ops per step, share of steps with no
10
+ op, unparsed commands), then per model: opportunities, hazards, structural
11
+ rate (Clopper-Pearson), automated classes, and hazards vs info.resolved.
12
+ Reads docs/audit/rq1/work/tarsur_raw/ (fetch_tarsur.py); writes work/tarsur.json.
13
+ """
14
+
15
+ import glob
16
+ import json
17
+ import os
18
+ import re
19
+ import sys
20
+ from collections import Counter, defaultdict
21
+
22
+ HERE = os.path.dirname(os.path.abspath(__file__))
23
+ sys.path.insert(0, HERE)
24
+ from common import WORK, dump # noqa: E402
25
+ import shellparse as sp # noqa: E402
26
+ from extract import shell_outcome, canonicalize, tok # noqa: E402
27
+ from score import Replay, cp_ci # noqa: E402
28
+ from materialize import classify # noqa: E402
29
+
30
+ RC = re.compile(r"^Exit code: (-?\d+)")
31
+
32
+
33
+ def adapt(d, cwd="/testbed"):
34
+ msgs = d.get("messages", [])
35
+ replies = {m.get("tool_call_id"): m for m in msgs if m.get("role") == "tool"}
36
+ steps, known, cov = [], {}, Counter()
37
+ for m in msgs:
38
+ if m.get("role") != "assistant":
39
+ continue
40
+ tcs = m.get("tool_calls") or []
41
+ if not tcs:
42
+ cov["assistant_without_tool_call"] += 1
43
+ continue
44
+ for tc in tcs:
45
+ cov["tool_calls"] += 1
46
+ fn = tc.get("function")
47
+ fn = fn.get("name") if isinstance(fn, dict) else fn
48
+ args = tc.get("arguments") or {}
49
+ if isinstance(args, str):
50
+ try:
51
+ args = json.loads(args)
52
+ except ValueError:
53
+ args = {}
54
+ cmd = args.get("command", "") if fn == "bash" else ""
55
+ cov[f"fn:{fn}"] += 1
56
+ r = replies.get(tc.get("id"))
57
+ obs = str((r or {}).get("content") or "")
58
+ mm = RC.match(obs)
59
+ rc = int(mm.group(1)) if mm else None
60
+ cov["reply_found"] += r is not None
61
+ ops, _, info = sp.extract(cmd, cwd, known, persist_cd=False) if cmd else ([], cwd, {})
62
+ cov["unparsed"] += bool(info.get("unparsed"))
63
+ if ops:
64
+ shell_outcome(ops, obs, rc, cmd)
65
+ steps.append({"i": len(steps), "unit": len(steps), "conv": 0, "bench": None, "tool": "shell",
66
+ "action": cmd, "thought": str(m.get("content") or "")[-1500:], "obs": obs, "rc": rc,
67
+ "cwd": cwd, "ran": True, "ops": ops, "tok_a": tok(cmd), "tok_o": tok(obs)})
68
+ return steps, cov
69
+
70
+
71
+ def main():
72
+ base = os.path.join(WORK, "tarsur_raw")
73
+ per_model = defaultdict(Counter)
74
+ classes = defaultdict(Counter)
75
+ resolved = defaultdict(lambda: defaultdict(list))
76
+ cov_all = defaultdict(Counter)
77
+ for f in sorted(glob.glob(os.path.join(base, "*", "*.traj.json"))):
78
+ model = f.split(os.sep)[-2]
79
+ d = json.load(open(f))
80
+ steps, cov = adapt(d)
81
+ cov_all[model].update(cov)
82
+ t = {"traj_id": os.path.basename(f), "agent": "mini-SWE-agent-2.1", "model": model, "steps": steps,
83
+ "final_diff": (d.get("info") or {}).get("submission") or None}
84
+ t["root"] = canonicalize(steps)
85
+ r = Replay(t).run()
86
+ c = per_model[model]
87
+ c["trajectories"] += 1
88
+ c["steps"] += len(steps)
89
+ c["zero_op_steps"] += sum(1 for s in steps if not s["ops"])
90
+ c["ops"] += sum(len(s["ops"]) for s in steps)
91
+ c["anchored_writes"] += sum(1 for s in steps for o in s["ops"] if o["op"] == "W" and o["kind"] == "anchored")
92
+ c["opportunities"] += len(r.opps)
93
+ c["hazards"] += len(r.hazards)
94
+ c["hazard_trajectories"] += bool(r.hazards)
95
+ for h in r.hazards:
96
+ cls, why, ev = classify(h, t)
97
+ classes[model][cls] += 1
98
+ res = bool((d.get("info") or {}).get("resolved"))
99
+ c["resolved"] += res
100
+ resolved[model]["haz" if r.hazards else "nohaz"].append(res)
101
+ out = {}
102
+ tot = Counter()
103
+ for model, c in sorted(per_model.items()):
104
+ tot.update(c)
105
+ rv = resolved[model]
106
+ out[model] = dict(c, rate=round(c["hazards"] / c["opportunities"], 4) if c["opportunities"] else None,
107
+ ci=cp_ci(c["hazards"], c["opportunities"]), classes=dict(classes[model]),
108
+ resolved_with_hazard=[sum(rv["haz"]), len(rv["haz"])],
109
+ resolved_without_hazard=[sum(rv["nohaz"]), len(rv["nohaz"])],
110
+ coverage=dict(cov_all[model]))
111
+ print(f"{model:24s} traj {c['trajectories']:4d} steps {c['steps']:6d} zero-op {c['zero_op_steps'] / max(1, c['steps']):.2f} "
112
+ f"opp {c['opportunities']:5d} haz {c['hazards']:4d} rate {out[model]['rate']} {out[model]['ci']} "
113
+ f"cls {dict(classes[model])} res(haz) {out[model]['resolved_with_hazard']} res(no) {out[model]['resolved_without_hazard']}")
114
+ pool(out)
115
+ print("ALL", out["ALL"])
116
+ dump(out, "tarsur.json")
117
+
118
+
119
+ # Configurations the dataset publishes twice: every trajectory of the first is
120
+ # an exact copy of the second's. Kept in the per-model rows, left out of ALL.
121
+ DUPLICATES = {"gpt-5.2-high": "gpt-5.2-codex"}
122
+
123
+ POOLED = ("trajectories", "steps", "zero_op_steps", "ops", "anchored_writes", "opportunities", "hazards",
124
+ "hazard_trajectories", "resolved")
125
+
126
+
127
+ def pool(out):
128
+ """Pool the per-model rows into ALL, skipping published duplicates."""
129
+ tot, allcls = Counter(), Counter()
130
+ for model, o in out.items():
131
+ if model == "ALL":
132
+ continue
133
+ if model in DUPLICATES:
134
+ o["duplicate_of"] = DUPLICATES[model]
135
+ continue
136
+ tot.update({k: o.get(k, 0) for k in POOLED})
137
+ allcls.update(o.get("classes", {}))
138
+ out["ALL"] = dict(tot, rate=round(tot["hazards"] / tot["opportunities"], 4) if tot["opportunities"] else None,
139
+ ci=cp_ci(tot["hazards"], tot["opportunities"]), classes=dict(allcls),
140
+ models=sum(1 for k in out if k != "ALL" and k not in DUPLICATES),
141
+ excluded_duplicates=sorted(DUPLICATES))
142
+
143
+
144
+ if __name__ == "__main__":
145
+ if sys.argv[1:] == ["--repool"]: # re-pool the saved per-model rows without the raw data
146
+ from common import WORK as _W
147
+ _out = json.load(open(os.path.join(_W, "tarsur.json")))
148
+ _out.pop("ALL", None)
149
+ pool(_out)
150
+ print("ALL", _out["ALL"])
151
+ dump(_out, "tarsur.json")
152
+ else:
153
+ main()
@@ -0,0 +1,3 @@
1
+ """Shared modules: trajectory parsers, replay metrics and harness hooks."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,13 @@
1
+ """Harness package for Phase 2 intervention experiments."""
2
+
3
+ from .freshness_hook import FreshnessHook, ObservationBinding
4
+ from .arms import InterventionArm, apply_intervention_arm
5
+ from .miniswe_scaffold import MiniSWEScaffold
6
+
7
+ __all__ = [
8
+ "FreshnessHook",
9
+ "ObservationBinding",
10
+ "InterventionArm",
11
+ "apply_intervention_arm",
12
+ "MiniSWEScaffold",
13
+ ]