Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,9 @@ this arrive through something the operator set up?*

1. **Signed intent ledger** (`~/.aegis/intent.jsonl`). The agent harness calls
`aegis.py intent hook <tool>` after each file-writing tool call; Aegis
appends one HMAC'd `{ts, path, sha256, tool}` record. A change whose content
appends one HMAC'd `{ts, path, sha256, tool}` record per written file — the
payload's `file_path`/`path`, or every Add/Update/Move-to target of a V4A
patch (Codex `apply_patch`, Hermes `patch`). A change whose content
hash matches a valid record is `self-attested` → **LOW**. This covers what
git cannot: untracked files and binaries outside any repo.
2. **Git self-vs-foreign**. A commit is `self-committed` → **LOW** only when two
Expand Down
48 changes: 44 additions & 4 deletions aegis.py
Original file line number Diff line number Diff line change
Expand Up @@ -17405,6 +17405,47 @@ def cmd_learn(argv):
return 1


_V4A_PATH = re.compile(r"\*\*\*\s*(Add File|Update File|Move to):\s*(.+?)\s*$")


def _v4a_patch_paths(text, cwd):
"""Files a V4A patch (Codex apply_patch, Hermes patch mode=patch) leaves
on disk: Add/Update targets, with an Update that is followed by a Move to
replaced by its destination. Delete File has no content to attest. Only
line-start markers count, so prose that quotes one is not a write."""
if not isinstance(text, str):
return []
out = []
for line in text.splitlines():
m = _V4A_PATH.match(line)
if not m:
continue
if m.group(1) == "Move to" and out:
out.pop()
out.append(os.path.normpath(os.path.join(cwd, m.group(2))))
return out


def _intent_hook_paths(payload):
"""The written file(s) named by an agent's post-tool-call JSON: a direct
path (Claude Code, Hermes write_file / patch mode=replace), else every
file a V4A patch in tool_input writes (Codex, Hermes patch mode=patch)."""
ti = payload.get("tool_input") or {}
if isinstance(ti, dict):
p = ti.get("file_path") or ti.get("path")
if p:
return [p]
texts = list(ti.values())
else:
texts = [ti]
cwd = payload.get("cwd") or os.getcwd()
out = []
for t in texts:
for s in (t if isinstance(t, list) else [t]):
out.extend(_v4a_patch_paths(s, cwd))
return out


def cmd_intent(argv):
"""CLI: `intent record <path> [tool]` | `intent hook <tool>` |
`intent list [n]`. Hook mode reads the harness's tool-call JSON on stdin,
Expand All @@ -17419,10 +17460,9 @@ def cmd_intent(argv):
tool = argv[3] if len(argv) > 3 else "agent"
try:
payload = json.loads(sys.stdin.read(1 << 20) or "{}")
ti = payload.get("tool_input") or {}
p = ti.get("file_path") or ti.get("path") or ""
if p and _intent_worthy(p):
intent_record(p, tool)
for p in _intent_hook_paths(payload):
if _intent_worthy(p):
intent_record(p, tool)
except Exception:
pass
return 0
Expand Down
54 changes: 54 additions & 0 deletions tests/test_custody.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,60 @@ def test_hook_mode_attests_stdin_payload_and_never_fails(self):
sys.stdin = saved
self.assertTrue(aegis._intent_attested(p, aegis.sha256(p)))

def _hook(self, payload, tool):
import io
saved = sys.stdin
try:
sys.stdin = io.StringIO(json.dumps(payload))
return aegis.cmd_intent(["aegis.py", "intent", "hook", tool])
finally:
sys.stdin = saved

def test_hook_mode_attests_every_file_a_v4a_patch_writes(self):
"""Codex's apply_patch and Hermes's patch(mode=patch) carry no
file_path: the paths live inside the patch text, relative to the
payload's cwd. Until 2026-09-24 neither agent's edits produced one
intent record, so their agent-config writes graded as unexplained."""
root = os.path.join(self.tmp, "agentroot")
upd = os.path.join(root, "config.toml")
add = os.path.join(root, "AGENTS.md")
moved = os.path.join(root, "hooks.json")
for p, body in ((upd, "[x]\n"), (add, "# a\n"), (moved, "{}")):
with open(p, "w") as f:
f.write(body)
patch = ("*** Begin Patch\n"
"*** Update File: config.toml\n@@\n-[y]\n+[x]\n"
"***Add File: %s\n+# a\n"
"*** Update File: hooks.old.json\n*** Move to: hooks.json\n"
"*** Delete File: old.json\n"
"*** End Patch" % add)
# Codex: patch text as a tool_input value, relative to cwd.
self.assertEqual(self._hook({"tool_name": "apply_patch", "cwd": root,
"tool_input": {"command": patch}},
"codex"), 0)
for p in (upd, add, moved):
self.assertTrue(aegis._intent_attested(p, aegis.sha256(p)), p)
# Delete File has no content to attest; the source of a move is gone.
self.assertEqual(aegis._v4a_patch_paths(patch, root),
[upd, add, moved])
# Codex freeform: tool_input may be the bare patch string.
with open(upd, "w") as f:
f.write("[z]\n")
self._hook({"tool_name": "apply_patch", "cwd": root,
"tool_input": patch}, "codex")
self.assertTrue(aegis._intent_attested(upd, aegis.sha256(upd)))
# Hermes: patch mode carries the text under "patch".
with open(add, "w") as f:
f.write("# b\n")
self._hook({"tool_name": "patch", "tool_input":
{"mode": "patch", "patch": patch}}, "hermes")
self.assertTrue(aegis._intent_attested(add, aegis.sha256(add)))

def test_v4a_paths_ignores_prose_that_merely_mentions_a_marker(self):
self.assertEqual(aegis._v4a_patch_paths(
"see *** Update File: x in the docs", "/r"), [])
self.assertEqual(aegis._v4a_patch_paths(12, "/r"), [])

def test_oversized_ledger_prunes_but_keeps_fresh_records(self):
p = os.path.join(self.tmp, "agentroot", "settings.json")
with open(p, "w") as f:
Expand Down
Loading