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
3 changes: 2 additions & 1 deletion argus/harness.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ def before_turn(messages):
if compacted is not messages:
messages[:] = compacted
if self.persist:
session.append(self.session_path, messages[0])
for message in messages:
session.append(self.session_path, message)
recorded_index = len(messages)
record()
return messages
Expand Down
2 changes: 2 additions & 0 deletions argus/loop.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""The agent turn loop with explicit tool-error containment."""

from .providers import Provider, ensure_call_ids, get_provider
from .tools import redact


def run(
Expand Down Expand Up @@ -63,6 +64,7 @@ def run(
result = str(tool.run(**args))
except Exception as error:
result = f"ERROR: {type(error).__name__}: {error}"
result = redact(result)
messages.append(
{
"role": "tool",
Expand Down
52 changes: 52 additions & 0 deletions tests/test_harness.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,58 @@ def test_compaction_is_persisted_and_reloadable(self):
self.assertTrue((resumed.messages[0].get("meta") or {}).get("compaction"))
self.assertEqual(harness.usage, {"input": 2, "output": 2, "total": 4})

def test_compaction_restores_retained_turns_on_resume(self):
provider = ScriptedProvider([
reply("", [tool_call("read_file", {"path": "note.txt"}, "c1")]),
reply("", [tool_call("read_file", {"path": "note.txt"}, "c2")]),
reply("compressed facts"),
reply("finished"),
])
harness = Harness(
self.root, provider=provider, policy=Policy("yolo"),
budget_tokens=1,
)
with mock.patch.object(context, "KEEP_RECENT", 3):
self.assertEqual(harness.run("long task"), "finished")

resumed = Harness(self.root, provider=ScriptedProvider(), persist=False)
self.assertTrue(resumed.resume(harness.session_path))
self.assertTrue((resumed.messages[0].get("meta") or {}).get("compaction"))
assistant_calls = [
call
for message in resumed.messages
if message.get("role") == "assistant"
for call in message.get("tool_calls") or []
]
tool_rows = [
message for message in resumed.messages if message.get("role") == "tool"
]
self.assertEqual([call["id"] for call in assistant_calls], ["c2"])
self.assertEqual([message["call_id"] for message in tool_rows], ["c2"])
self.assertEqual(resumed.messages[-1]["text"], "finished")

def test_use_skill_output_is_redacted_before_persistence(self):
secret = "sk-abcdefghijklmnop1234"
skill_dir = os.path.join(self.root, "skills", "demo")
os.makedirs(skill_dir)
with open(os.path.join(skill_dir, "SKILL.md"), "w", encoding="utf-8") as stream:
stream.write(f"---\ndescription: Demo skill\n---\nUse {secret}.\n")
provider = ScriptedProvider([
reply("", [tool_call("use_skill", {"name": "demo"}, "c1")]),
reply("done"),
])
harness = Harness(self.root, provider=provider, policy=Policy("yolo"))
harness.run("load the demo skill")

tool_message = next(
message for message in harness.messages if message.get("role") == "tool"
)
self.assertIn("[REDACTED_OPENAI_KEY]", tool_message["text"])
with open(harness.session_path, encoding="utf-8") as stream:
persisted = stream.read()
self.assertNotIn(secret, persisted)
self.assertIn("[REDACTED_OPENAI_KEY]", persisted)

def test_remember_tool_writes_and_dedupes(self):
harness = Harness(
self.root, provider=ScriptedProvider(), persist=False
Expand Down
17 changes: 17 additions & 0 deletions tests/test_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,23 @@ def test_unknown_tool_is_reported_to_model(self):
loop.run("system", messages, self.tools, provider=provider)
self.assertTrue(messages[2]["text"].startswith("ERROR: unknown tool"))

def test_tool_results_are_redacted_in_history_and_events(self):
secret = "sk-abcdefghijklmnop1234"
provider = ScriptedProvider([
reply("", [tool_call("echo", {"text": secret}, "c1")]),
reply("done"),
])
messages = [{"role": "user", "text": "go"}]
events = []
loop.run(
"system", messages, self.tools, provider=provider,
on_event=lambda kind, payload: events.append((kind, payload)),
)
self.assertNotIn(secret, messages[2]["text"])
self.assertIn("[REDACTED_OPENAI_KEY]", messages[2]["text"])
tool_end = next(payload for kind, payload in events if kind == "tool_end")
self.assertNotIn(secret, tool_end["result"])

def test_tool_exception_is_contained(self):
provider = ScriptedProvider([
reply("", [tool_call("boom", {}, "c1")]),
Expand Down
Loading