Problem
agentwatch/core/safety.py renders the human-approval prompt with 13 direct print() calls
(around L889):
print("\n" + "=" * 60)
print("⚠️ AGENTWATCH SAFETY CHECK")
print("=" * 60)
print(f"Agent: {event.agent_name or event.agent_id}")
print(f"Risk Level: {safety.risk_level.value.upper()}")
...
Credit where it's due — this already guards correctly:
if not sys.stdin.isatty():
print("Non-interactive session detected. Blocking action.")
It fails closed in non-interactive sessions, which is the right call for a safety tool and means
it won't hang in CI or a daemon. So this isn't a hang bug.
The narrower issue is that it writes to stdout unconditionally, from a library module.
agentwatch is a package people embed inside their agents, and a library that prints to stdout:
- corrupts structured output. Any consumer emitting JSON on stdout now has a decorated ASCII box
in the middle of it.
- can't be suppressed or redirected. There's no logger to configure, no handler to attach, no
verbosity to turn down.
- can't be routed. A hosted deployment wants this in its log aggregator, not on a stdout nobody
reads. Right now there's no seam to do that.
Note the non-interactive branch is the one that hurts most: that's precisely the headless,
embedded, machine-consumed context, and it's the one that still prints.
Suggested fix
Keep the behaviour exactly as it is — including the fail-closed default — but move the output
behind a seam:
- route the messages through
logging (the rest of the library already does), and/or
- accept an optional
on_prompt / renderer callback so the CLI can pass in its Rich renderer and
an embedded consumer can pass in their own (or nothing).
The CLI keeps its current pretty box; embedders get a stdout they own. Zero change to what gets
blocked or approved.
Problem
agentwatch/core/safety.pyrenders the human-approval prompt with 13 directprint()calls(around L889):
Credit where it's due — this already guards correctly:
It fails closed in non-interactive sessions, which is the right call for a safety tool and means
it won't hang in CI or a daemon. So this isn't a hang bug.
The narrower issue is that it writes to stdout unconditionally, from a library module.
agentwatchis a package people embed inside their agents, and a library that prints to stdout:in the middle of it.
verbosity to turn down.
reads. Right now there's no seam to do that.
Note the non-interactive branch is the one that hurts most: that's precisely the headless,
embedded, machine-consumed context, and it's the one that still prints.
Suggested fix
Keep the behaviour exactly as it is — including the fail-closed default — but move the output
behind a seam:
logging(the rest of the library already does), and/oron_prompt/renderercallback so the CLI can pass in its Rich renderer andan embedded consumer can pass in their own (or nothing).
The CLI keeps its current pretty box; embedders get a stdout they own. Zero change to what gets
blocked or approved.