agent-loop-guard-runtime 0.6.0a2__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 (76) hide show
  1. agent_loop_guard_runtime-0.6.0a2.dist-info/METADATA +407 -0
  2. agent_loop_guard_runtime-0.6.0a2.dist-info/RECORD +76 -0
  3. agent_loop_guard_runtime-0.6.0a2.dist-info/WHEEL +5 -0
  4. agent_loop_guard_runtime-0.6.0a2.dist-info/entry_points.txt +2 -0
  5. agent_loop_guard_runtime-0.6.0a2.dist-info/licenses/LICENSE +202 -0
  6. agent_loop_guard_runtime-0.6.0a2.dist-info/top_level.txt +1 -0
  7. app/__init__.py +6 -0
  8. app/api/__init__.py +1 -0
  9. app/api/admin_routes.py +179 -0
  10. app/api/anthropic_routes.py +21 -0
  11. app/api/common.py +457 -0
  12. app/api/mcp_routes.py +218 -0
  13. app/api/openai_routes.py +26 -0
  14. app/api/replay_routes.py +202 -0
  15. app/api/ui_routes.py +295 -0
  16. app/benchmark/__init__.py +2 -0
  17. app/benchmark/adapters.py +97 -0
  18. app/benchmark/data/starter-v1.json +38 -0
  19. app/benchmark/dataset.py +59 -0
  20. app/benchmark/models.py +46 -0
  21. app/benchmark/runner.py +63 -0
  22. app/benchmark/scorers.py +34 -0
  23. app/benchmark/statistics.py +48 -0
  24. app/benchmark/storage.py +78 -0
  25. app/cli.py +624 -0
  26. app/core/config.py +196 -0
  27. app/core/demo.py +109 -0
  28. app/core/loop_detector.py +120 -0
  29. app/core/policy_engine.py +167 -0
  30. app/core/redaction.py +84 -0
  31. app/core/security.py +54 -0
  32. app/core/token_meter.py +67 -0
  33. app/db/models.py +296 -0
  34. app/db/repository.py +1488 -0
  35. app/db/session.py +57 -0
  36. app/main.py +59 -0
  37. app/mcp/__init__.py +1 -0
  38. app/mcp/gateway.py +230 -0
  39. app/mcp/policy.py +281 -0
  40. app/mcp/presets/development.yml +25 -0
  41. app/mcp/presets/filesystem.yml +18 -0
  42. app/mcp/stdio.py +142 -0
  43. app/platform/__init__.py +1 -0
  44. app/platform/alembic/__init__.py +2 -0
  45. app/platform/alembic/env.py +38 -0
  46. app/platform/alembic/script.py.mako +24 -0
  47. app/platform/alembic/versions/0001_initial_schema.py +18 -0
  48. app/platform/alembic/versions/__init__.py +2 -0
  49. app/platform/events.py +44 -0
  50. app/platform/maintenance.py +138 -0
  51. app/platform/migrations.py +24 -0
  52. app/platform/setup.py +92 -0
  53. app/providers/__init__.py +5 -0
  54. app/providers/base.py +23 -0
  55. app/providers/mock.py +169 -0
  56. app/providers/upstream.py +101 -0
  57. app/replay/__init__.py +1 -0
  58. app/replay/costs.py +37 -0
  59. app/replay/formats.py +87 -0
  60. app/replay/sdk.py +102 -0
  61. app/sandbox/__init__.py +2 -0
  62. app/sandbox/policy.py +22 -0
  63. app/sandbox/workspace.py +281 -0
  64. app/static/styles.css +327 -0
  65. app/templates/agents.html +48 -0
  66. app/templates/base.html +27 -0
  67. app/templates/dashboard.html +55 -0
  68. app/templates/demo.html +36 -0
  69. app/templates/mcp.html +69 -0
  70. app/templates/policies.html +30 -0
  71. app/templates/replay.html +63 -0
  72. app/templates/replay_compare.html +74 -0
  73. app/templates/replay_detail.html +130 -0
  74. app/templates/session_detail.html +71 -0
  75. app/templates/sessions.html +24 -0
  76. app/templates/settings.html +20 -0
@@ -0,0 +1,55 @@
1
+ {% extends "base.html" %}
2
+ {% block title %}Dashboard · Agent Loop Guard{% endblock %}
3
+ {% block content %}
4
+ <header class="page-head">
5
+ <h1>Dashboard</h1>
6
+ <form action="/demo/run" method="post">
7
+ <input type="hidden" name="scenario" value="exact-loop">
8
+ <input type="hidden" name="mode" value="shadow">
9
+ <button type="submit">Run Demo</button>
10
+ </form>
11
+ </header>
12
+
13
+ <section class="metric-grid">
14
+ <div class="metric"><span>Sessions</span><strong>{{ stats.sessions }}</strong></div>
15
+ <div class="metric"><span>Requests</span><strong>{{ stats.requests }}</strong></div>
16
+ <div class="metric"><span>Traces</span><strong>{{ stats.traces }}</strong></div>
17
+ <div class="metric"><span>Spans</span><strong>{{ stats.spans }}</strong></div>
18
+ <div class="metric"><span>Flags</span><strong>{{ stats.flags }}</strong></div>
19
+ <div class="metric"><span>Blocks</span><strong>{{ stats.blocks }}</strong></div>
20
+ <div class="metric"><span>Tokens</span><strong>{{ stats.tokens }}</strong></div>
21
+ </section>
22
+
23
+ <section class="panel">
24
+ <h2>Modules</h2>
25
+ <table>
26
+ <thead><tr><th>Module</th><th>Interface</th><th>Local status</th></tr></thead>
27
+ <tbody>
28
+ <tr><td>Loop Guard</td><td><a href="/sessions">Sessions</a></td><td><span class="status status-ok">ready</span></td></tr>
29
+ <tr><td>MCP Firewall</td><td><a href="/mcp">Approvals and audit</a></td><td>{{ pending_approvals }} pending</td></tr>
30
+ <tr><td>Replay</td><td><a href="/replay">Trace explorer</a></td><td><span class="status status-ok">ready</span></td></tr>
31
+ <tr><td>Benchmark Lab</td><td><code>alg bench</code></td><td><span class="status status-ok">ready</span></td></tr>
32
+ <tr><td>Sandbox</td><td><code>alg sandbox</code></td><td><span class="status status-{{ 'ok' if docker_available else 'paused' }}">{{ 'Docker found' if docker_available else 'Docker required' }}</span></td></tr>
33
+ </tbody>
34
+ </table>
35
+ </section>
36
+
37
+ <section class="panel">
38
+ <h2>Recent Sessions</h2>
39
+ <table>
40
+ <thead><tr><th>ID</th><th>Status</th><th>Requests</th><th>Flags</th><th>Blocks</th><th>Tokens</th></tr></thead>
41
+ <tbody>
42
+ {% for session in sessions %}
43
+ <tr>
44
+ <td><a href="/sessions/{{ session.id }}">{{ session.id }}</a></td>
45
+ <td><span class="status status-{{ session.status }}">{{ session.status }}</span></td>
46
+ <td>{{ session.request_count }}</td>
47
+ <td>{{ session.flagged_count }}</td>
48
+ <td>{{ session.blocked_count }}</td>
49
+ <td>{{ session.total_tokens }}</td>
50
+ </tr>
51
+ {% endfor %}
52
+ </tbody>
53
+ </table>
54
+ </section>
55
+ {% endblock %}
@@ -0,0 +1,36 @@
1
+ {% extends "base.html" %}
2
+ {% block title %}Demo Lab · Agent Loop Guard{% endblock %}
3
+ {% block content %}
4
+ <header class="page-head"><h1>Demo Lab</h1></header>
5
+ <section class="panel demo-grid">
6
+ {% for scenario in ["normal", "exact-loop", "tool-loop", "error-retry", "streaming"] %}
7
+ <form action="/demo/run" method="post">
8
+ <input type="hidden" name="scenario" value="{{ scenario }}">
9
+ <label>{{ scenario }}</label>
10
+ <select name="mode">
11
+ <option value="shadow">shadow</option>
12
+ <option value="enforce">enforce</option>
13
+ </select>
14
+ <button type="submit">Run</button>
15
+ </form>
16
+ {% endfor %}
17
+ </section>
18
+ <section class="panel">
19
+ <h2>Recent Demo Sessions</h2>
20
+ <table>
21
+ <thead><tr><th>ID</th><th>Status</th><th>Requests</th><th>Flags</th><th>Blocks</th></tr></thead>
22
+ <tbody>
23
+ {% for session in sessions %}
24
+ <tr>
25
+ <td><a href="/sessions/{{ session.id }}">{{ session.id }}</a></td>
26
+ <td>{{ session.status }}</td>
27
+ <td>{{ session.request_count }}</td>
28
+ <td>{{ session.flagged_count }}</td>
29
+ <td>{{ session.blocked_count }}</td>
30
+ </tr>
31
+ {% endfor %}
32
+ </tbody>
33
+ </table>
34
+ </section>
35
+ {% endblock %}
36
+
app/templates/mcp.html ADDED
@@ -0,0 +1,69 @@
1
+ {% extends "base.html" %}
2
+ {% block title %}MCP Firewall - Agent Loop Guard{% endblock %}
3
+ {% block content %}
4
+ <header class="page-head">
5
+ <div>
6
+ <h1>MCP Permission Firewall</h1>
7
+ <p class="muted">Policy: <code>{{ policy_path }}</code></p>
8
+ </div>
9
+ <a class="button secondary" href="/api/v1/mcp/export">Export Audit</a>
10
+ </header>
11
+
12
+ <section class="panel">
13
+ <h2>Pending Approvals</h2>
14
+ {% for approval in approvals if approval.status == "pending" %}
15
+ <form class="approval-row" method="post" action="/mcp/approvals/{{ approval.id }}/decision">
16
+ <div>
17
+ <strong>{{ approval.id }}</strong>
18
+ <div class="muted">Expires {{ approval.expires_at }}</div>
19
+ </div>
20
+ <select name="scope" aria-label="Approval scope">
21
+ <option value="once">Allow once</option>
22
+ <option value="session">Allow for session</option>
23
+ </select>
24
+ <button type="submit" name="action" value="allow">Allow</button>
25
+ <button class="danger" type="submit" name="action" value="deny">Deny</button>
26
+ </form>
27
+ {% else %}
28
+ <p class="muted">No tool calls are waiting for approval.</p>
29
+ {% endfor %}
30
+ </section>
31
+
32
+ <section class="panel">
33
+ <h2>Servers</h2>
34
+ <table>
35
+ <thead><tr><th>Name</th><th>Transport</th><th>Target</th><th>Status</th></tr></thead>
36
+ <tbody>
37
+ {% for server in servers %}
38
+ <tr>
39
+ <td>{{ server.name }}<br><span class="muted">{{ server.id }}</span></td>
40
+ <td>{{ server.transport }}</td>
41
+ <td><code>{{ server.target }}</code></td>
42
+ <td><span class="status status-{{ 'ok' if server.enabled else 'ended' }}">{{ 'enabled' if server.enabled else 'disabled' }}</span></td>
43
+ </tr>
44
+ {% endfor %}
45
+ </tbody>
46
+ </table>
47
+ </section>
48
+
49
+ <section class="panel">
50
+ <h2>Decision Audit</h2>
51
+ <table>
52
+ <thead><tr><th>Time</th><th>Server / Tool</th><th>Action</th><th>Rule</th><th>Reason</th><th>Trace</th></tr></thead>
53
+ <tbody>
54
+ {% for event in events %}
55
+ <tr>
56
+ <td>{{ event.created_at }}</td>
57
+ <td>{{ event.server_id }}<br><code>{{ event.tool_name }}</code></td>
58
+ <td><span class="status status-{{ 'blocked' if event.action == 'deny' else 'ok' }}">{{ event.action }}</span></td>
59
+ <td>{{ event.rule_id or '-' }}<br><span class="muted">{{ event.policy_version }}</span></td>
60
+ <td>{{ event.reason }}</td>
61
+ <td>{% if event.trace_id %}<a href="/replay/{{ event.trace_id }}">Open</a>{% else %}-{% endif %}</td>
62
+ </tr>
63
+ {% else %}
64
+ <tr><td colspan="6" class="muted">No MCP decisions recorded.</td></tr>
65
+ {% endfor %}
66
+ </tbody>
67
+ </table>
68
+ </section>
69
+ {% endblock %}
@@ -0,0 +1,30 @@
1
+ {% extends "base.html" %}
2
+ {% block title %}Policies · Agent Loop Guard{% endblock %}
3
+ {% block content %}
4
+ <header class="page-head"><h1>Policies</h1></header>
5
+ <section class="panel">
6
+ <form action="/policies/{{ project_id }}/update" method="post">
7
+ <table>
8
+ <thead><tr><th>Rule</th><th>Threshold</th><th>Window</th><th>Action</th><th>Enabled</th></tr></thead>
9
+ <tbody>
10
+ {% for policy in policies %}
11
+ <tr>
12
+ <td><code>{{ policy.rule_type }}</code></td>
13
+ <td><input type="number" min="1" name="{{ policy.id }}_threshold" value="{{ policy.threshold }}"></td>
14
+ <td><input type="number" min="0" name="{{ policy.id }}_window_size" value="{{ policy.window_size }}"></td>
15
+ <td>
16
+ <select name="{{ policy.id }}_action">
17
+ <option value="block" {% if policy.action == "block" %}selected{% endif %}>block</option>
18
+ <option value="flag" {% if policy.action == "flag" %}selected{% endif %}>flag</option>
19
+ </select>
20
+ </td>
21
+ <td><input type="checkbox" name="{{ policy.id }}_enabled" {% if policy.enabled %}checked{% endif %}></td>
22
+ </tr>
23
+ {% endfor %}
24
+ </tbody>
25
+ </table>
26
+ <div class="form-actions"><button type="submit">Save Policies</button></div>
27
+ </form>
28
+ </section>
29
+ {% endblock %}
30
+
@@ -0,0 +1,63 @@
1
+ {% extends "base.html" %}
2
+ {% block title %}Replay - Agent Loop Guard{% endblock %}
3
+ {% block content %}
4
+ <header class="page-head">
5
+ <h1>Replay</h1>
6
+ <a class="button secondary" href="/api/v1/runs">Open API</a>
7
+ </header>
8
+
9
+ <section class="panel">
10
+ <form class="inline-form" action="/replay" method="get">
11
+ <input name="q" value="{{ q }}" placeholder="Search trace, task, agent, model, failure tag">
12
+ <input name="project_id" value="{{ project_id }}" placeholder="Project ID">
13
+ <button type="submit">Search</button>
14
+ </form>
15
+ </section>
16
+
17
+ <section class="panel">
18
+ <h2>Compare Runs</h2>
19
+ <form class="inline-form" action="/replay/compare" method="post">
20
+ <input name="left_trace_id" placeholder="Baseline trace id">
21
+ <input name="right_trace_id" placeholder="Candidate trace id">
22
+ <button type="submit">Compare</button>
23
+ </form>
24
+ </section>
25
+
26
+ <section class="panel">
27
+ <h2>Trace Runs</h2>
28
+ <table>
29
+ <thead>
30
+ <tr>
31
+ <th>ID</th>
32
+ <th>Status</th>
33
+ <th>Task</th>
34
+ <th>Agent</th>
35
+ <th>Model</th>
36
+ <th>Duration</th>
37
+ <th>Tokens</th>
38
+ <th>Spans</th>
39
+ <th>Events</th>
40
+ <th>Failure</th>
41
+ </tr>
42
+ </thead>
43
+ <tbody>
44
+ {% for run in runs %}
45
+ <tr>
46
+ <td>{% if run.pinned %}<span title="Pinned">&#9733;</span> {% endif %}<a href="/replay/{{ run.id }}">{{ run.id }}</a></td>
47
+ <td><span class="status status-{{ run.status }}">{{ run.status }}</span></td>
48
+ <td>{{ run.task_id or "-" }}</td>
49
+ <td>{{ run.agent_name or "-" }}</td>
50
+ <td>{{ run.model or "-" }}</td>
51
+ <td>{{ run.duration_ms }} ms</td>
52
+ <td>{{ run.total_tokens }}</td>
53
+ <td>{{ run.span_count }}</td>
54
+ <td>{{ run.event_count }}</td>
55
+ <td>{{ run.failure_tag or "-" }}</td>
56
+ </tr>
57
+ {% else %}
58
+ <tr><td colspan="10" class="muted">No trace runs yet. Send traffic through the proxy or POST to /api/v1/traces.</td></tr>
59
+ {% endfor %}
60
+ </tbody>
61
+ </table>
62
+ </section>
63
+ {% endblock %}
@@ -0,0 +1,74 @@
1
+ {% extends "base.html" %}
2
+ {% block title %}Trace Compare - Agent Loop Guard{% endblock %}
3
+ {% block content %}
4
+ <header class="page-head">
5
+ <h1>Trace Compare</h1>
6
+ <a class="button secondary" href="/replay">Back to Replay</a>
7
+ </header>
8
+
9
+ <section class="panel">
10
+ <h2>Runs</h2>
11
+ <table>
12
+ <thead><tr><th></th><th>ID</th><th>Status</th><th>Task</th><th>Agent</th><th>Model</th></tr></thead>
13
+ <tbody>
14
+ <tr>
15
+ <th>Baseline</th>
16
+ <td><a href="/replay/{{ result.left.id }}">{{ result.left.id }}</a></td>
17
+ <td>{{ result.left.status }}</td>
18
+ <td>{{ result.left.task_id or "-" }}</td>
19
+ <td>{{ result.left.agent_name or "-" }}</td>
20
+ <td>{{ result.left.model or "-" }}</td>
21
+ </tr>
22
+ <tr>
23
+ <th>Candidate</th>
24
+ <td><a href="/replay/{{ result.right.id }}">{{ result.right.id }}</a></td>
25
+ <td>{{ result.right.status }}</td>
26
+ <td>{{ result.right.task_id or "-" }}</td>
27
+ <td>{{ result.right.agent_name or "-" }}</td>
28
+ <td>{{ result.right.model or "-" }}</td>
29
+ </tr>
30
+ </tbody>
31
+ </table>
32
+ </section>
33
+
34
+ <section class="panel">
35
+ <h2>Aligned Steps</h2>
36
+ <table>
37
+ <thead><tr><th>Step</th><th>Baseline</th><th>Candidate</th><th>Duration Delta</th></tr></thead>
38
+ <tbody>
39
+ {% for step in result.aligned_steps %}
40
+ <tr>
41
+ <td><code>{{ step.step }}</code></td>
42
+ <td>{{ step.left_status or '-' }} / {{ step.left_duration_ms if step.left_duration_ms is not none else '-' }} ms</td>
43
+ <td>{{ step.right_status or '-' }} / {{ step.right_duration_ms if step.right_duration_ms is not none else '-' }} ms</td>
44
+ <td>{{ step.duration_delta_ms if step.duration_delta_ms is not none else '-' }}</td>
45
+ </tr>
46
+ {% endfor %}
47
+ </tbody>
48
+ </table>
49
+ </section>
50
+
51
+ <section class="panel">
52
+ <h2>Delta</h2>
53
+ <table>
54
+ <thead><tr><th>Metric</th><th>Candidate - Baseline</th></tr></thead>
55
+ <tbody>
56
+ {% for key, value in result.delta.items() %}
57
+ <tr><td>{{ key }}</td><td>{{ value }}</td></tr>
58
+ {% endfor %}
59
+ </tbody>
60
+ </table>
61
+ </section>
62
+
63
+ <section class="panel">
64
+ <h2>Span Mix</h2>
65
+ <table>
66
+ <thead><tr><th>Span Name</th><th>Count Delta</th></tr></thead>
67
+ <tbody>
68
+ {% for key, value in result.span_name_delta.items() %}
69
+ <tr><td><code>{{ key }}</code></td><td>{{ value }}</td></tr>
70
+ {% endfor %}
71
+ </tbody>
72
+ </table>
73
+ </section>
74
+ {% endblock %}
@@ -0,0 +1,130 @@
1
+ {% extends "base.html" %}
2
+ {% block title %}Trace {{ run.id }} - Agent Loop Guard{% endblock %}
3
+ {% block content %}
4
+ <header class="page-head">
5
+ <div>
6
+ <h1>Trace {{ run.id }}</h1>
7
+ <p class="muted">Task {{ run.task_id or "unknown" }} / {{ run.agent_name or "unknown agent" }}</p>
8
+ </div>
9
+ <div class="actions">
10
+ <form method="post" action="/replay/{{ run.id }}/pin">
11
+ <input type="hidden" name="pinned" value="{{ 'false' if run.pinned else 'true' }}">
12
+ <button class="secondary" type="submit">{{ 'Unpin' if run.pinned else 'Pin' }}</button>
13
+ </form>
14
+ <a class="button secondary" href="/api/v1/runs/{{ run.id }}/export?format=json">JSON</a>
15
+ <a class="button secondary" href="/api/v1/runs/{{ run.id }}/export?format=jsonl">JSONL</a>
16
+ <a class="button secondary" href="/api/v1/runs/{{ run.id }}/export?format=otel">OTel</a>
17
+ {% if run.source_session_id %}
18
+ <a class="button secondary" href="/sessions/{{ run.source_session_id }}">Source Session</a>
19
+ {% endif %}
20
+ </div>
21
+ </header>
22
+
23
+ <section class="metric-grid">
24
+ <div class="metric"><span>Status</span><strong>{{ run.status }}</strong></div>
25
+ <div class="metric"><span>Duration</span><strong>{{ run.duration_ms }} ms</strong></div>
26
+ <div class="metric"><span>Tokens</span><strong>{{ run.total_tokens }}</strong></div>
27
+ <div class="metric"><span>Spans</span><strong>{{ run.span_count }}</strong></div>
28
+ <div class="metric"><span>Events</span><strong>{{ run.event_count }}</strong></div>
29
+ <div class="metric"><span>Cost</span><strong>{{ '%.4f'|format(run.total_cost_micros / 1000000) }}</strong></div>
30
+ </section>
31
+
32
+ <section class="panel">
33
+ <h2>Run Metadata</h2>
34
+ <table class="kv">
35
+ <tbody>
36
+ <tr><th>Project</th><td>{{ run.project_id }}</td></tr>
37
+ <tr><th>Model</th><td>{{ run.model or "-" }}</td></tr>
38
+ <tr><th>Failure tag</th><td>{{ run.failure_tag or "-" }}</td></tr>
39
+ <tr><th>Created</th><td>{{ run.created_at }}</td></tr>
40
+ <tr><th>Updated</th><td>{{ run.updated_at }}</td></tr>
41
+ <tr><th>Attributes</th><td><code>{{ run.attributes | tojson }}</code></td></tr>
42
+ </tbody>
43
+ </table>
44
+ </section>
45
+
46
+ <section class="panel">
47
+ <h2>Timeline</h2>
48
+ <div class="timeline" aria-label="Trace timeline">
49
+ {% for span in spans %}
50
+ <div class="timeline-row" style="--depth: {{ span.depth }}">
51
+ <code>{{ span.name }}</code>
52
+ <div class="timeline-track">
53
+ <span class="timeline-bar status-{{ span.status }}" style="left: {{ span.offset_pct }}%; width: {{ span.width_pct }}%" title="{{ span.name }}: {{ span.duration_ms }} ms"></span>
54
+ </div>
55
+ <span>{{ span.duration_ms }} ms</span>
56
+ </div>
57
+ {% endfor %}
58
+ </div>
59
+ <table>
60
+ <thead>
61
+ <tr>
62
+ <th>Span</th>
63
+ <th>Status</th>
64
+ <th>Duration</th>
65
+ <th>Parent</th>
66
+ <th>Start</th>
67
+ <th>Key Details</th>
68
+ </tr>
69
+ </thead>
70
+ <tbody>
71
+ {% for span in spans %}
72
+ <tr>
73
+ <td><code>{{ span.name }}</code><br><span class="muted">{{ span.id }}</span></td>
74
+ <td><span class="status status-{{ span.status }}">{{ span.status }}</span></td>
75
+ <td>{{ span.duration_ms }} ms</td>
76
+ <td>{{ span.parent_span_id or "root" }}</td>
77
+ <td>{{ span.start_at }}</td>
78
+ <td>
79
+ {% if span.attributes.get("request.id") %}request {{ span.attributes.get("request.id") }}{% endif %}
80
+ {% if span.attributes.get("http.status_code") %} / HTTP {{ span.attributes.get("http.status_code") }}{% endif %}
81
+ {% if span.attributes.get("policy.decision") %} / {{ span.attributes.get("policy.decision") }}{% endif %}
82
+ {% if span.attributes.get("policy.reason") %} / {{ span.attributes.get("policy.reason") }}{% endif %}
83
+ </td>
84
+ </tr>
85
+ {% else %}
86
+ <tr><td colspan="6" class="muted">No spans recorded.</td></tr>
87
+ {% endfor %}
88
+ </tbody>
89
+ </table>
90
+ </section>
91
+
92
+ <section class="panel">
93
+ <h2>Events</h2>
94
+ <table>
95
+ <thead><tr><th>Time</th><th>Name</th><th>Severity</th><th>Span</th><th>Attributes</th></tr></thead>
96
+ <tbody>
97
+ {% for event in events %}
98
+ <tr>
99
+ <td>{{ event.timestamp }}</td>
100
+ <td><code>{{ event.name }}</code></td>
101
+ <td>{{ event.severity }}</td>
102
+ <td>{{ event.span_id or "-" }}</td>
103
+ <td><code>{{ event.attributes | tojson }}</code></td>
104
+ </tr>
105
+ {% else %}
106
+ <tr><td colspan="5" class="muted">No events recorded.</td></tr>
107
+ {% endfor %}
108
+ </tbody>
109
+ </table>
110
+ </section>
111
+
112
+ <section class="panel">
113
+ <h2>Artifacts</h2>
114
+ <table>
115
+ <thead><tr><th>Path</th><th>MIME</th><th>Size</th><th>SHA-256</th></tr></thead>
116
+ <tbody>
117
+ {% for artifact in artifacts %}
118
+ <tr>
119
+ <td>{{ artifact.path }}</td>
120
+ <td>{{ artifact.mime_type or "-" }}</td>
121
+ <td>{{ artifact.size }}</td>
122
+ <td>{{ artifact.sha256 or "-" }}</td>
123
+ </tr>
124
+ {% else %}
125
+ <tr><td colspan="4" class="muted">No artifacts recorded.</td></tr>
126
+ {% endfor %}
127
+ </tbody>
128
+ </table>
129
+ </section>
130
+ {% endblock %}
@@ -0,0 +1,71 @@
1
+ {% extends "base.html" %}
2
+ {% block title %}Session {{ session.id }} · Agent Loop Guard{% endblock %}
3
+ {% block content %}
4
+ <header class="page-head">
5
+ <div>
6
+ <h1>{{ session.name or session.id }}</h1>
7
+ <p class="muted">{{ session.project_id }} · {{ session.agent_id }} · {{ session.status }}</p>
8
+ </div>
9
+ <div class="actions">
10
+ <form action="/sessions/{{ session.id }}/pause" method="post"><button type="submit">Stop</button></form>
11
+ <form action="/sessions/{{ session.id }}/resume" method="post"><button type="submit">Resume</button></form>
12
+ <a class="button secondary" href="/api/export/sessions/{{ session.id }}.json">Export JSON</a>
13
+ </div>
14
+ </header>
15
+
16
+ <section class="metric-grid">
17
+ <div class="metric"><span>Requests</span><strong>{{ session.request_count }}</strong></div>
18
+ <div class="metric"><span>Input</span><strong>{{ session.input_tokens }}</strong></div>
19
+ <div class="metric"><span>Output</span><strong>{{ session.output_tokens }}</strong></div>
20
+ <div class="metric"><span>Flags</span><strong>{{ session.flagged_count }}</strong></div>
21
+ <div class="metric"><span>Blocks</span><strong>{{ session.blocked_count }}</strong></div>
22
+ </section>
23
+
24
+ <section class="panel">
25
+ <h2>Session Notes</h2>
26
+ <form class="inline-form" action="/sessions/{{ session.id }}/edit" method="post">
27
+ <input name="name" value="{{ session.name or '' }}" placeholder="Name">
28
+ <input name="note" value="{{ session.note or '' }}" placeholder="Note">
29
+ <button type="submit">Save</button>
30
+ </form>
31
+ </section>
32
+
33
+ <section class="panel">
34
+ <h2>Timeline</h2>
35
+ <table>
36
+ <thead><tr><th>Time</th><th>Type</th><th>Severity</th><th>Reason</th><th>Details</th></tr></thead>
37
+ <tbody>
38
+ {% for event in events %}
39
+ <tr>
40
+ <td>{{ event.created_at }}</td>
41
+ <td>{{ event.type }}</td>
42
+ <td>{{ event.severity }}</td>
43
+ <td><code>{{ event.reason_code or "" }}</code></td>
44
+ <td><code>{{ event.details }}</code></td>
45
+ </tr>
46
+ {% endfor %}
47
+ </tbody>
48
+ </table>
49
+ </section>
50
+
51
+ <section class="panel">
52
+ <h2>Requests</h2>
53
+ <table>
54
+ <thead><tr><th>Time</th><th>Endpoint</th><th>Status</th><th>Decision</th><th>Reason</th><th>Tokens</th><th>Latency</th></tr></thead>
55
+ <tbody>
56
+ {% for item in requests %}
57
+ <tr>
58
+ <td>{{ item.created_at }}</td>
59
+ <td>{{ item.endpoint }}</td>
60
+ <td>{{ item.status }}</td>
61
+ <td>{{ item.decision }}</td>
62
+ <td><code>{{ item.reason_code or "" }}</code></td>
63
+ <td>{{ item.total_tokens }}</td>
64
+ <td>{{ item.latency_ms }} ms</td>
65
+ </tr>
66
+ {% endfor %}
67
+ </tbody>
68
+ </table>
69
+ </section>
70
+ {% endblock %}
71
+
@@ -0,0 +1,24 @@
1
+ {% extends "base.html" %}
2
+ {% block title %}Sessions · Agent Loop Guard{% endblock %}
3
+ {% block content %}
4
+ <header class="page-head"><h1>Sessions</h1></header>
5
+ <section class="panel">
6
+ <table>
7
+ <thead><tr><th>ID</th><th>Name</th><th>Status</th><th>Agent</th><th>Requests</th><th>Reason</th><th>Updated</th></tr></thead>
8
+ <tbody>
9
+ {% for session in sessions %}
10
+ <tr>
11
+ <td><a href="/sessions/{{ session.id }}">{{ session.id }}</a></td>
12
+ <td>{{ session.name or "" }}</td>
13
+ <td><span class="status status-{{ session.status }}">{{ session.status }}</span></td>
14
+ <td>{{ session.agent_id }}</td>
15
+ <td>{{ session.request_count }}</td>
16
+ <td>{% if session.blocked_count %}blocked{% elif session.flagged_count %}flagged{% else %}allow{% endif %}</td>
17
+ <td>{{ session.updated_at }}</td>
18
+ </tr>
19
+ {% endfor %}
20
+ </tbody>
21
+ </table>
22
+ </section>
23
+ {% endblock %}
24
+
@@ -0,0 +1,20 @@
1
+ {% extends "base.html" %}
2
+ {% block title %}Settings · Agent Loop Guard{% endblock %}
3
+ {% block content %}
4
+ <header class="page-head"><h1>Settings</h1></header>
5
+ <section class="panel">
6
+ <table class="kv">
7
+ <tbody>
8
+ <tr><th>Host</th><td>{{ config.host }}</td></tr>
9
+ <tr><th>Port</th><td>{{ config.port }}</td></tr>
10
+ <tr><th>Storage</th><td><code>{{ config.storage_url }}</code></td></tr>
11
+ <tr><th>Default mode</th><td>{{ config.default_mode }}</td></tr>
12
+ <tr><th>Default provider</th><td>{{ config.default_provider }}</td></tr>
13
+ <tr><th>Full content logging</th><td>{{ config.full_content_logging }}</td></tr>
14
+ <tr><th>Product telemetry</th><td>disabled</td></tr>
15
+ <tr><th>MCP policy</th><td><code>{{ config.mcp_policy_path }}</code></td></tr>
16
+ <tr><th>Body limit</th><td>{{ config.body_limit_bytes }} bytes</td></tr>
17
+ </tbody>
18
+ </table>
19
+ </section>
20
+ {% endblock %}