The `explore` subagent was **incomplete** — it hit the sync tool-round limit and did not produce a final answer.

However, the tool results from its partial run contain useful evidence. From the grep output, I can see where subagent profiles are referenced:

**Key findings from the evidence:**

| What | Where |
|------|-------|
| **Profile lists (hardcoded)** | `scripts/matrix-lite-approval-policy-mcp.py` lines 96–113 — defines `test-runner`, `review`, `review-deep`, `debug`, `explore`, `subagent` in approval policy logic |
| **Profile configuration** | `config/opencode/` — multiple `.opencode.json` files define agent modes and permissions (e.g., `smoke-agent`, `persistent-chat`, `persistent-chat-reflection`) |
| **Profile behavior/rules** | `config/opencode/smoke-agent-skills/` and `config/opencode/shared/agents/` — Markdown files like `explore.md`, `debug.md`, `plan.md`, etc. define prompts and tool permissions |
| **Subagent spawn/scheduler logic** | `scripts/matrix-lite-status-mcp.py` — contains `subagent_spawn()`, `subagent_list()`, `subagent_status()`, `subagent_log()` functions (evidenced by test references at lines 2072, 2376, 2396, etc.) |

The subagent did not finish to report the **exact file/function** where profiles are defined. Based on the grep evidence, profile definitions appear to be **distributed**:
- **Behavior/prompts**: `config/opencode/shared/agents/{profile}.md`
- **Permissions/tools**: `config/opencode/*.opencode.json` files
- **Runtime spawn logic**: `scripts/matrix-lite-status-mcp.py`

I will not claim the subagent completed successfully. Want me to inspect `scripts/matrix-lite-status-mcp.py` or the `config/opencode/shared/agents/` directory directly to pinpoint the exact definition?

Native identity and behavior

Generic smoke/native worker profile

Native tool use contract

Persistent-chat prompt port placeholder

MCP proxy capability index

Agent skill lifecycle placeholder

Pinned state

Decision state

Curated memory

Session packet candidate

Session-start/restore packet candidate

Current task

Longer-term ideas / experiments

Runtime hints and notices

Operational notices

Recent failures

Persistent context manifest health

Native scoped grants

Provider capability probes

Native session summary

Native hot tail

Bounded keyword retrieval

Vector search / RAG placeholder

Turn packet candidate

Matrix attachment/reply/batch context candidate

Native skill: prompt-governance

Native skill: senior-architect

Current Matrix event context

Output:
The `explore` subagent was **incomplete** — it hit the sync tool-round limit and did not produce a final answer.

However, the tool results from its partial run contain useful evidence. From the grep output, I can see where subagent profiles are referenced:

**Key findings from the evidence:**

| What | Where |
|------|-------|
| **Profile lists (hardcoded)** | `scripts/matrix-lite-approval-policy-mcp.py` lines 96–113 — defines `test-runner`, `review`, `review-deep`, `debug`, `explore`, `subagent` in approval policy logic |
| **Profile configuration** | `config/opencode/` — multiple `.opencode.json` files define agent modes and permissions (e.g., `smoke-agent`, `persistent-chat`, `persistent-chat-reflection`) |
| **Profile behavior/rules** | `config/opencode/smoke-agent-skills/` and `config/opencode/shared/agents/` — Markdown files like `explore.md`, `debug.md`, `plan.md`, etc. define prompts and tool permissions |
| **Subagent spawn/scheduler logic** | `scripts/matrix-lite-status-mcp.py` — contains `subagent_spawn()`, `subagent_list()`, `subagent_status()`, `subagent_log()` functions (evidenced by test references at lines 2072, 2376, 2396, etc.) |

The subagent did not finish to report the **exact file/function** where profiles are defined. Based on the grep evidence, profile definitions appear to be **distributed**:
- **Behavior/prompts**: `config/opencode/shared/agents/{profile}.md`
- **Permissions/tools**: `config/opencode/*.opencode.json` files
- **Runtime spawn logic**: `scripts/matrix-lite-status-mcp.py`

I will not claim the subagent completed successfully. Want me to inspect `scripts/matrix-lite-status-mcp.py` or the `config/opencode/shared/agents/` directory directly to pinpoint the exact definition?

Output: Incomplete: native subagent reached its sync tool-round limit and finalization failed. Inspect the job log and rerun with a narrower prompt or async mode.

Summary: Incomplete: native subagent reached its sync tool-round limit and finalization failed. Inspect the job log and rerun with a narrower prompt or async mode.

scripts/matrix-lite-vision-mcp.py:53:    "github-copilot/gemini-3-flash-preview",
scripts/matrix-lite-vision-mcp.py:76:    "gemini-flash": "github-copilot/gemini-3-flash-preview",
scripts/matrix-lite-vision-mcp.py:77:    "gemini-3-flash-preview": "github-copilot/gemini-3-flash-preview",
config/opencode/smoke-agent-skills/senior-architect/scripts/architecture_diagram_generator.py:562:  %(prog)s ./my-project --format plantuml --type layer
config/opencode/smoke-agent-skills/senior-architect/scripts/architecture_diagram_generator.py:572:  plantuml    - PlantUML format
config/opencode/smoke-agent-skills/senior-architect/scripts/architecture_diagram_generator.py:583:        choices=['mermaid', 'plantuml', 'ascii'],
config/opencode/smoke-agent-skills/senior-architect/scripts/architecture_diagram_generator.py:644:        'plantuml': PlantUMLGenerator,
tests/test_continuity_scripts.py:247:            'memory_candidates_review_packet',
tests/test_continuity_scripts.py:251:            'semantic_candidates_review_packet',
tests/test_continuity_scripts.py:270:                summary='Preserve current debugging context',
tests/test_continuity_scripts.py:281:            self.assertIn('Preserve current debugging context', markdown)
tests/test_continuity_scripts.py:305:                'ts': '2026-05-28T00:00:00Z', 'key': 'mem1', 'status': 'candidate', 'reason': 'test', 'text': 'memory helper review packet'
tests/test_continuity_scripts.py:327:                packet = mem.semantic_candidates_review_packet(status=['candidate'], category=['workflow_signal'])
tests/test_continuity_scripts.py:330:                mem_packet = mem.memory_candidates_review_packet(status=['candidate'])
tests/test_continuity_scripts.py:346:            self.assertEqual(background['agents']['semantic_candidate_curation']['open_review_rows'], 0)
tests/test_continuity_scripts.py:348:            self.assertEqual(background['agents']['semantic_candidate_curation']['explicit_cursor']['open_review_rows'], 0)
tests/test_continuity_scripts.py:349:            self.assertEqual(background['agents']['memory_candidate_curation']['open_review_rows'], 1)
tests/test_continuity_scripts.py:501:            planned = mem.embed_index(include_sources=['curated'], max_embeddings=10)
tests/test_continuity_scripts.py:502:            self.assertTrue(planned.get('needs_confirmation'))
tests/test_continuity_scripts.py:675:        self.assertIn('matrix_lite_status_subagent_log', prompt)
tests/test_continuity_scripts.py:715:    def test_project_repo_permissions_available_to_persistent_and_subagents(self):
tests/test_continuity_scripts.py:929:    def test_persistent_profiles_use_extended_proxy_for_context7_and_gh_grep(self):
tests/test_continuity_scripts.py:954:    def test_persistent_profiles_keep_only_expected_primary_agents(self):
tests/test_continuity_scripts.py:963:            for removed in ('build-fast', 'plan', 'title', 'summary', 'compaction'):
tests/test_continuity_scripts.py:965:            self.assertNotIn('plan', data.get('mode', {}), str(config))
tests/test_continuity_scripts.py:975:        self.assertEqual(smoke.get('direct_acp_profile'), 'smoke-agent')
tests/test_continuity_scripts.py:978:        for profile in ('smoke-agent', 'smoke-agent-temp01', 'smoke-agent-temp02', 'smoke-agent-temp05'):
tests/test_continuity_scripts.py:979:            opencode = json.loads((APP / 'config' / 'opencode' / f'{profile}.opencode.json').read_text())
tests/test_continuity_scripts.py:980:            self.assertFalse(opencode.get('mcp', {}).get('approval_policy', {}).get('enabled'), profile)
tests/test_continuity_scripts.py:982:            self.assertIs(broker.get('enabled'), True, profile)
tests/test_continuity_scripts.py:986:                profile,
tests/test_continuity_scripts.py:989:            self.assertEqual(broker.get('environment', {}).get('MATRIX_LITE_PRIVILEGED_BROKER_ROOM'), 'matrix-lite:phase1', profile)
tests/test_continuity_scripts.py:999:                            self.assertIs(tools.get(tool_name), True, (profile, section, name, tool_name))
tests/test_continuity_scripts.py:1011:        self.assertEqual(persistent.get('direct_acp_profile'), 'persistent-chat')
tests/test_continuity_scripts.py:1122:        self.assertIn('inputPreview', text)
tests/test_continuity_scripts.py:1123:        self.assertIn('previewPermissionValue', text)
tests/test_continuity_scripts.py:1211:    def test_reflection_profile_uses_model_heartbeat_with_native_subagents(self):
tests/test_continuity_scripts.py:1216:            'explore': 'allow',
tests/test_continuity_scripts.py:1217:            'debug': 'allow',
tests/test_continuity_scripts.py:1220:            'test-runner': 'allow',
tests/test_continuity_scripts.py:1221:            'review': 'allow',
tests/test_continuity_scripts.py:1222:            'review-deep': 'allow',
tests/test_continuity_scripts.py:1233:                self.assertEqual(agents[name].get('mode'), 'subagent')
tests/test_continuity_scripts.py:1237:        self.assertIn('You may spawn bounded native task subagents', reflection)
tests/test_continuity_scripts.py:1238:        self.assertNotIn('- Do not spawn subagents.', reflection)
tests/test_continuity_scripts.py:1240:    def test_model_heartbeat_can_use_same_native_subagents(self):
tests/test_continuity_scripts.py:1245:            'explore': 'allow',
tests/test_continuity_scripts.py:1246:            'debug': 'allow',
tests/test_continuity_scripts.py:1249:            'test-runner': 'allow',
tests/test_continuity_scripts.py:1250:            'review': 'allow',
tests/test_continuity_scripts.py:1251:            'review-deep': 'allow',
tests/test_continuity_scripts.py:1261:                self.assertEqual(agents[name].get('mode'), 'subagent')
tests/test_continuity_scripts.py:1284:                for agent_name in ('explore', 'review', 'model-heartbeat', 'implementer'):
tests/test_continuity_scripts.py:1289:                    self.assertEqual(decision(rules, '/home/gleb/.config/opencode/shared/agents/explore.md'), 'allow', agent_name)
tests/test_continuity_scripts.py:1327:                for name in ('matrix_lite_status_timer_list', 'matrix_lite_status_timer_get', 'matrix_lite_status_subagent_list', 'matrix_lite_status_subagent_status', 'matrix_lite_status_subagent_log'):
tests/test_continuity_scripts.py:1330:                for name in ('matrix_lite_status_subagent_spawn', 'matrix_lite_status_subagent_kill', 'matrix_lite_status_timer_create_wakeup', 'matrix_lite_status_subagent_timer_create'):
tests/test_continuity_scripts.py:1333:                self.assertEqual(agents.get('explore', {}).get('permission', {}).get('edit'), 'deny')
tests/test_continuity_scripts.py:1334:                self.assertEqual(agents.get('review', {}).get('permission', {}).get('write'), 'deny')
tests/test_continuity_scripts.py:1343:                self.assertEqual(task_policy.get('test-runner'), 'deny')
tests/test_continuity_scripts.py:1346:                    self.assertEqual(task_policy.get('explore-lite'), 'allow')
tests/test_continuity_scripts.py:1347:                    self.assertIn('explore-lite', agents)
tests/test_continuity_scripts.py:1348:                    self.assertEqual(agents.get('explore-lite', {}).get('permission', {}).get('edit'), 'deny')
tests/test_continuity_scripts.py:1349:                    self.assertEqual(agents.get('explore-lite', {}).get('permission', {}).get('write'), 'deny')
tests/test_continuity_scripts.py:1350:                    self.assertIn('Use sync `explore-lite`', agent.get('prompt', ''))
tests/test_continuity_scripts.py:1351:                    explore_lite = agents.get('explore-lite', {})
tests/test_continuity_scripts.py:1352:                    self.assertNotEqual(explore_lite.get('tools', {}).get('matrix_lite_status_subagent_spawn'), True)
tests/test_continuity_scripts.py:1353:                    self.assertNotEqual(explore_lite.get('tools', {}).get('matrix_lite_status_subagent_kill'), True)
tests/test_continuity_scripts.py:1354:                    self.assertEqual(explore_lite.get('permission', {}).get('matrix_lite_status_subagent_spawn'), 'deny')
tests/test_continuity_scripts.py:1355:                    self.assertEqual(explore_lite.get('permission', {}).get('task'), 'deny')
tests/test_continuity_scripts.py:1356:                    self.assertEqual(explore_lite.get('permission', {}).get('bash', {}).get('*'), 'deny')
tests/test_continuity_scripts.py:1357:                    self.assertEqual(explore_lite.get('permission', {}).get('bash', {}).get('/home/matrix-lite/app/scripts/matrix-lite-status-mcp.py *'), 'deny')
tests/test_continuity_scripts.py:1375:    def test_persistent_subagents_are_noninteractive_with_diagnostics(self):
tests/test_continuity_scripts.py:1377:            'explore', 'debug', 'test-runner', 'review', 'review-deep',
tests/test_continuity_scripts.py:1452:    def test_sync_script_requires_all_referenced_opencode_profiles(self):
tests/test_continuity_scripts.py:1455:        for profile in ('persistent-chat', 'persistent-chat-reflection', 'smoke-agent', 'smoke-agent-temp01', 'smoke-agent-temp02', 'smoke-agent-temp05', 'topic-chat'):
tests/test_continuity_scripts.py:1456:            self.assertIn(profile, sync_text)
tests/test_continuity_scripts.py:1457:            self.assertTrue((APP / 'config/opencode' / f'{profile}.opencode.json').exists(), profile)
tests/test_continuity_scripts.py:1458:        for agent_id in ('opencode-subagent-temp01', 'opencode-subagent-temp02', 'opencode-subagent-temp05'):
tests/test_continuity_scripts.py:1472:            for profile in ('persistent-chat', 'persistent-chat-reflection', 'smoke-agent', 'smoke-agent-temp01', 'smoke-agent-temp02', 'smoke-agent-temp05', 'topic-chat'):
tests/test_continuity_scripts.py:1473:                (app / 'config/opencode' / f'{profile}.opencode.json').write_text('{}')
tests/test_continuity_scripts.py:1928:                summary='test-runner needs approval for validation check',
tests/test_continuity_scripts.py:1932:                session_label='test-runner',
tests/test_continuity_scripts.py:1959:                    'session_label': 'test-runner',
tests/test_continuity_scripts.py:2035:    def test_subagent_spawn_eta_defaults_to_matrix_timer_channel(self):
tests/test_continuity_scripts.py:2048:            status = load_script('status_subagent_eta_test', 'scripts/matrix-lite-status-mcp.py')
tests/test_continuity_scripts.py:2056:            status.SUBAGENT_STATE_DIR = status.WORKSPACE / 'state' / 'subagents'
tests/test_continuity_scripts.py:2072:                result = status.subagent_spawn(
tests/test_continuity_scripts.py:2075:                temp_result = status.subagent_spawn(
tests/test_continuity_scripts.py:2081:            self.assertEqual(result['spawned']['agent_id'], 'opencode-subagent')
tests/test_continuity_scripts.py:2084:            self.assertEqual(temp_result['spawned']['agent_id'], 'opencode-subagent-temp02')
tests/test_continuity_scripts.py:2251:        explore_secret = status.permission_preflight(path='/home/matrix-lite/secrets/matrix-lite.env', namespace='external_directory', agent='explore')
tests/test_continuity_scripts.py:2252:        self.assertFalse(explore_secret.get('allowed'))
tests/test_continuity_scripts.py:2253:        explore_auth = status.permission_preflight(path='/home/gleb/.local/share/opencode/auth.json', namespace='external_directory', agent='explore')
tests/test_continuity_scripts.py:2254:        self.assertFalse(explore_auth.get('allowed'))
tests/test_continuity_scripts.py:2255:        explore_videoctl = status.permission_preflight(path='/home/gleb/videoctl/pyproject.toml', namespace='external_directory', agent='explore')
tests/test_continuity_scripts.py:2256:        self.assertTrue(any(c.get('namespace') == 'external_directory' and c.get('decision') == 'allow' for c in explore_videoctl.get('checks', [])))
tests/test_continuity_scripts.py:2257:        explore_git_diff = status.permission_preflight(command='git -c safe.directory=/home/gleb/videoctl -C /home/gleb/videoctl diff --stat', agent='explore')
tests/test_continuity_scripts.py:2258:        self.assertTrue(explore_git_diff.get('allowed'))
tests/test_continuity_scripts.py:2259:        explore_commit = status.permission_preflight(command='git -C /home/gleb/videoctl commit -m x', agent='explore')
tests/test_continuity_scripts.py:2260:        self.assertFalse(explore_commit.get('allowed'))
tests/test_continuity_scripts.py:2368:    def test_subagent_list_ignores_result_artifacts(self):
tests/test_continuity_scripts.py:2369:        status = load_script('matrix_lite_status_subagent_list_test', 'scripts/matrix-lite-status-mcp.py')
tests/test_continuity_scripts.py:2376:                result = status.subagent_list(limit=10)
tests/test_continuity_scripts.py:2383:    def test_subagent_status_returns_safe_result_payload(self):
tests/test_continuity_scripts.py:2384:        status = load_script('matrix_lite_status_subagent_status_test', 'scripts/matrix-lite-status-mcp.py')
tests/test_continuity_scripts.py:2396:                result = status.subagent_status('abcdef123456')
tests/test_continuity_scripts.py:2404:    def test_subagent_status_ignores_out_of_root_result_path(self):
tests/test_continuity_scripts.py:2405:        status = load_script('matrix_lite_status_subagent_status_escape_test', 'scripts/matrix-lite-status-mcp.py')
tests/test_continuity_scripts.py:2408:            status.SUBAGENT_STATE_DIR = Path(td) / 'subagents'
tests/test_continuity_scripts.py:2420:                result = status.subagent_status('abcdef123456')
config/opencode/smoke-agent-skills/incident-commander/scripts/severity_classifier.py:8:routing, SLA impact assessments, and immediate action plans.
config/opencode/smoke-agent-skills/incident-commander/scripts/severity_classifier.py:22:    build_action_plan()   - Generate immediate action checklist
config/opencode/smoke-agent-skills/incident-commander/scripts/severity_classifier.py:623:def build_action_plan(
config/opencode/smoke-agent-skills/incident-commander/scripts/severity_classifier.py:630:    """Generate the immediate action plan for the classified incident."""
config/opencode/smoke-agent-skills/incident-commander/scripts/severity_classifier.py:688:    comms.append(f"Schedule post-incident review within 48 hours")
config/opencode/smoke-agent-skills/incident-commander/scripts/severity_classifier.py:834:    action_plan: ActionPlan,
config/opencode/smoke-agent-skills/incident-commander/scripts/severity_classifier.py:919:    for i, action in enumerate(action_plan.immediate_actions, 1):
config/opencode/smoke-agent-skills/incident-commander/scripts/severity_classifier.py:924:    for i, step in enumerate(action_plan.diagnostic_steps, 1):
config/opencode/smoke-agent-skills/incident-commander/scripts/severity_classifier.py:929:    for i, action in enumerate(action_plan.communication_actions, 1):
config/opencode/smoke-agent-skills/incident-commander/scripts/severity_classifier.py:933:    rb = action_plan.rollback_assessment
config/opencode/smoke-agent-skills/incident-commander/scripts/severity_classifier.py:970:    action_plan: ActionPlan,
config/opencode/smoke-agent-skills/incident-commander/scripts/severity_classifier.py:980:        "action_plan": asdict(action_plan),
config/opencode/smoke-agent-skills/incident-commander/scripts/severity_classifier.py:990:    action_plan: ActionPlan,
config/opencode/smoke-agent-skills/incident-commander/scripts/severity_classifier.py:1084:    for i, action in enumerate(action_plan.immediate_actions, 1):
config/opencode/smoke-agent-skills/incident-commander/scripts/severity_classifier.py:1090:    for i, step in enumerate(action_plan.diagnostic_steps, 1):
config/opencode/smoke-agent-skills/incident-commander/scripts/severity_classifier.py:1096:    for i, action in enumerate(action_plan.communication_actions, 1):
config/opencode/smoke-agent-skills/incident-commander/scripts/severity_classifier.py:1100:    rb = action_plan.rollback_assessment
config/opencode/smoke-agent-skills/incident-commander/scripts/severity_classifier.py:1205:    action_plan = build_action_plan(severity_score, incident, impact, signals, context)
config/opencode/smoke-agent-skills/incident-commander/scripts/severity_classifier.py:1210:        output = format_json(incident, severity_score, escalation, action_plan, sla_impact)
config/opencode/smoke-agent-skills/incident-commander/scripts/severity_classifier.py:1212:        output = format_markdown(incident, severity_score, escalation, action_plan, sla_impact)
config/opencode/smoke-agent-skills/incident-commander/scripts/severity_classifier.py:1214:        output = format_text(incident, severity_score, escalation, action_plan, sla_impact)
scripts/matrix-lite-approval-policy-mcp.py:96:    "test-runner",
scripts/matrix-lite-approval-policy-mcp.py:97:    "review",
scripts/matrix-lite-approval-policy-mcp.py:98:    "review-deep",
scripts/matrix-lite-approval-policy-mcp.py:101:    "debug",
scripts/matrix-lite-approval-policy-mcp.py:102:    "explore",
scripts/matrix-lite-approval-policy-mcp.py:109:    "test-runner",
scripts/matrix-lite-approval-policy-mcp.py:113:    "subagent",
scripts/matrix-lite-approval-policy-mcp.py:337:    request_item: dict[str, Any], event: str, reviewer: str = ""
scripts/matrix-lite-approval-policy-mcp.py:349:        f"Reviewer: {reviewer or request_item.get('reviewer') or 'unknown'}\n"
scripts/matrix-lite-approval-policy-mcp.py:651:    request_id: str, mode: str = "once", ttl_minutes: int = 1440, reviewer: str = "Gleb"
scripts/matrix-lite-approval-policy-mcp.py:662:    request["reviewer"] = reviewer
scripts/matrix-lite-approval-policy-mcp.py:683:            "reviewer": reviewer,
scripts/matrix-lite-approval-policy-mcp.py:692:            message=_approval_wakeup_message(request, f"granted {mode}", reviewer),
scripts/matrix-lite-approval-policy-mcp.py:711:def deny(request_id: str, reviewer: str = "Gleb", reason: str = "") -> dict[str, Any]:
scripts/matrix-lite-approval-policy-mcp.py:720:            "reviewer": reviewer,
scripts/matrix-lite-approval-policy-mcp.py:734:            message=_approval_wakeup_message(request, "denied", reviewer),
config/opencode/smoke-agent-skills/skill-security-auditor/scripts/skill_security_auditor.py:13:    2 = WARN (review manually before installing)
config/opencode/smoke-agent-skills/skill-security-auditor/scripts/skill_security_auditor.py:309:        "regex": r"(?:open|write|Path)\s*\([^)]*(?:\.bashrc|\.bash_profile|\.profile|\.zshrc|\.zprofile)",  # noqa: SEC-AUDITOR
config/opencode/smoke-agent-skills/skill-security-auditor/scripts/skill_security_auditor.py:751:                        fix="Move dependencies to requirements.txt for pre-install review",
config/opencode/smoke-agent-skills/skill-security-auditor/scripts/skill_security_auditor.py:763:                        fix="Move dependencies to package.json for pre-install review",
config/opencode/smoke-agent-skills/incident-commander/scripts/incident_timeline_builder.py:7:professional reports suitable for post-incident review and stakeholder briefing.
config/opencode/smoke-agent-skills/incident-commander/scripts/incident_timeline_builder.py:372:                   f"A post-incident review will be published within 48 hours.")
config/opencode/smoke-agent-skills/incident-commander/scripts/incident_timeline_builder.py:401:                     f"We apologize for any inconvenience and are reviewing to prevent recurrence.")
scripts/matrix-lite-timer-dispatch.py:23:SUBAGENT_STATE_DIR = WORKSPACE / "state" / "subagents"
scripts/matrix-lite-timer-dispatch.py:151:                "mode": str(agent.get("profile") or "persistent"),
scripts/matrix-lite-timer-dispatch.py:877:def subagent_status_text(timer: dict[str, Any]) -> tuple[str, bool, bool]:
scripts/matrix-lite-timer-dispatch.py:879:    job_id = str(payload.get("subagent_job_id") or "")
scripts/matrix-lite-timer-dispatch.py:882:            "Timer: background task check\nNo subagent job id was stored.",
scripts/matrix-lite-timer-dispatch.py:897:    label = job

2:
3: import fcntl
4: import fnmatch
5: import json
6: import os
7: import re
8: import time
9: import uuid
10: from datetime import datetime, timedelta, timezone
11: from html import escape
12: from pathlib import Path
13: from typing import Any
14: from urllib import parse, request
15: from urllib.error import HTTPError, URLError
16:
17: from mcp.server.fastmcp import FastMCP
18:
19:
20: mcp = FastMCP("Matrix-lite Approval Policy", json_response=True)
21:
22: STATE_DIR = Path(
23:     os.environ.get(
24:         "MATRIX_LITE_APPROVAL_STATE_DIR", "/home/matrix-lite/state/approvals"
25:     )
26: )
27: REQUESTS_FILE = STATE_DIR / "requests.json"
28: POLICY_FILE = STATE_DIR / "policy.json"
29: WORKSPACE = Path(os.environ.get("MATRIX_LITE_WORKSPACE_DIR", "/home/matrix-lite/workspace"))
30: TIMER_STATE_DIR = WORKSPACE / "state" / "timers"
31: TIMERS_FILE = TIMER_STATE_DIR / "timers.json"
32: TIMER_LOCK_FILE = TIMER_STATE_DIR / ".timers.lock"
33: CONFIG_FILE = Path("/home/matrix-lite/config/matrix-lite.json")
34: SELF_CONSUMING_APPROVAL_TOOLS = {"persistent-chat-approved-patch"}
35: SELF_CONSUMING_DEFAULT_SESSION_LABELS = {
36:     "persistent-chat-approved-patch": "persistent-chat"
37: }
38: SELF_CONSUMING_SCOPE_PATTERNS = {
39:     (
40:         "persistent-chat-approved-patch",
41:         "apply-approved-patch",
42:     ): r"patch://sha256:[0-9a-f]{64}",
43:     (
44:         "persistent-chat-approved-patch",
45:         "install-file",
46:     ): r"install-file://sha256:[0-9a-f]{64}",
47:     (
48:         "persistent-chat-approved-patch",
49:         "run-maintenance",
50:     ): r"maintenance://sha256:[0-9a-f]{64}",
51:     (
52:         "persistent-chat-approved-patch",
53:         "systemctl",
54:     ): r"systemd://(?:system|user)/(?:restart|reload)/[A-Za-z0-9_.@-]+",
55:     (
56:         "persistent-chat-approved-patch",
57:         "gentle-reload",
58:     ): r"/home/matrix-lite/app/scripts/persistent-chat-reload",
59: }
60: SELF_CONSUMING_SCOPE_EXAMPLES = {
61:     (
62:         "persistent-chat-approved-patch",
63:         "apply-approved-patch",
64:     ): "patch://sha256:<64-hex>",
65:     (
66:         "persistent-chat-approved-patch",
67:         "install-file",
68:     ): "install-file://sha256:<64-hex>",
69:     (
70:         "persistent-chat-approved-patch",
71:         "run-maintenance",
72:     ): "maintenance://sha256:<64-hex>",
73:     (
74:         "persistent-chat-approved-patch",
75:         "systemctl",
76:     ): "systemd://system/restart/matrix-lite.service",
77:     (
78:         "persistent-chat-approved-patch",
79:         "gentle-reload",
80:     ): "/home/matrix-lite/app/scripts/persistent-chat-reload",
81: }
82: SESSION_ALIASES = {
83:     "persistent-chat": "persistent-chat",
84:     "persistent-chat": "persistent-chat",
85:     "chat": "topic-chat",
86:     "topic-chat": "topic-chat",
87:     "approvals": "approvals",
88:     "approvals": "approvals",
89:     "work": "topic-chat",
90:     "topic-chat": "topic-chat",
91: }
92: BACKGROUND_DEFERRED_SESSION_LABELS = {
93:     "model-heartbeat",
94:     "operational-synthesis",
95:     "persistent-chat-model-heartbeat",
96:     "test-runner",
97:     "review",
98:     "review-deep",
99:     "requirements-auditor",
100:     "requirements-auditor-deep",
101:     "debug",
102:     "explore",
103:     "rubberduck",
104: }
105: BACKGROUND_DEFERRED_MARKERS = (
106:     "model-heartbeat",
107:     "operational-synthesis",
108:     "background maintenance",
109:     "test-runner",
110:     "test runner",
111:     "deterministic check",
112:     "validation check",
113:     "subagent",
114: )
115:
116:
117: def _load(path: Path, default: Any) -> Any:
118:     try:
119:         return json.loads(path.read_text(encoding="utf-8"))
120:     except FileNotFoundError:
121:         return default

122:
123:
124: def _save(path: Path, data: Any) -> None:
125:     STATE_DIR.mkdir(parents=True, exist_ok=True)
126:     STATE_DIR.chmod(0o700)
127:     tmp = path.with_name(f".{path.name}.{os.getpid()}.tmp")
128:     tmp.write_text(
129:         json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8"
130:     )
131:     tmp.chmod(0o600)
132:     tmp.replace(path)
133:
134:
135: def _now() -> float:
136:     return time.time()
137:
138:
139: def _utc_timestamp(dt: datetime | None = None) -> str:
140:     return (
141:         (dt or datetime.now(timezone.utc))
142:         .astimezone(timezone.utc)
143:         .isoformat()
144:         .replace("+00:00", "Z")
145:     )
146:
147:
148: def _resolve_timer_binding(label: str) -> str:
149:     clean = str(label or "").strip()
150:     return SESSION_ALIASES.get(clean, clean)
151:
152:
153: def _default_request_session_label(tool: str, session_label: str) -> str:
154:     clean = str(session_label or "").strip()
155:     if clean:
156:         return clean
157:     return SELF_CONSUMING_DEFAULT_SESSION_LABELS.get(str(tool or "").strip(), "")
158:
159:
160: def _is_background_deferred_request(item: dict[str, Any]) -> bool:
161:     """Return true for unattended maintenance approvals that should not time out turns.
162:
163:     Curator/synthesis/heartbeat/test work may ask for approval during unattended
164:     turns, and Gleb may approve hours later. Do not create timeout/grant wakeups
165:     for those requests: future background turns should list/check approvals and
166:     act only after a grant.
167:     """
168:     label = str(item.get("session_label") or "").strip().lower()
169:     tool = str(item.get("tool") or "").strip().lower()
170:     summary = str(item.get("summary") or "").strip().lower()
171:     scope = str(item.get("scope") or "").strip().lower()
172:     haystack = "\n".join([label, tool, summary, scope])
173:     if label in BACKGROUND_DEFERRED_SESSION_LABELS:
174:         return True
175:     return any(marker in haystack for marker in BACKGROUND_DEFERRED_MARKERS)
176:
177:
178: def _invalid_self_consuming_scope_message(
179:     tool: str, action: str, scope: str
180: ) -> str | None:
181:     clean_tool = str(tool or "").strip()
182:     if clean_tool not in SELF_CONSUMING_APPROVAL_TOOLS:
183:         return None
184:     clean_action = str(action or "").strip()
185:     clean_scope = str(scope or "").strip()
186:     pattern = SELF_CONSUMING_SCOPE_PATTERNS.get((clean_tool, clean_action))
187:     if pattern and re.fullmatch(pattern, clean_scope):
188:         return None
189:     if pattern:
190:         expected = SELF_CONSUMING_SCOPE_EXAMPLES.get(
191:             (clean_tool, clean_action), pattern
192:         )
193:         return (
194:             f"invalid scope for {clean_tool} {clean_action}: use the exact wrapper command with --describe "
195:             f"and request approval with its recommended_scope (or exact single scope item), not a plain file path. "
196:             f"Expected scope format similar to: {expected}"
197:         )
198:     return None
199:
200:
201:
202:
203: def _binding_label_for_room(room_id: str) -> str:
204:     wanted_room = str(room_id or "").strip()
205:     if not wanted_room.startswith("!"):
206:         return ""
207:     try:
208:         cfg = json.loads(CONFIG_FILE.read_text(encoding="utf-8"))
209:     except Exception:
210:         return ""
211:     conversations = cfg.get("conversations") if isinstance(cfg.get("conversations"), dict) else {}
212:     agents = cfg.get("agents") if isinstance(cfg.get("agents"), dict) else {}
213:     for key, conv in conversations.items():
214:         if not isinstance(conv, dict) or str(conv.get("room_id") or "") != wanted_room:
215:             continue
216:         agent_key = str(conv.get("agent") or "")
217:         agent = agents.get(agent_key) if isinstance(agents, dict) else {}
218:         label = str(agent.get("session_label") or agent_key or key).strip()
219:         if label:
220:             return label
221:     return ""
222:
223: def _binding_exists(label: str, channel: str = "matrix") -> bool:
224:     if channel != "matrix":
225:         return False
226:     wanted = _resolve_timer_binding(label)
227:     try:
228:         cfg = json.loads(CONFIG_FILE.read_text(encoding="utf-8"))
229:     except Exception:
230:         return False
231:     conversations = cfg.get("conversations") if isinstance(cfg.get("conversations"), dict) else {}
232:     agents = cfg.get("agents") if isinstance(cfg.get("agents"), dict) else {}
233:     for key, conv in conversations.items():
234:         if not isinstance(conv, dict):
235:             continue
236:         agent_key = str(conv.get("agent") or "")
237:         agent = agents.get(agent_key) if isinstance(agents, dict) else {}
238:         labels = {str(key), agent_key, str(agent.get("session_label") or "")}
239:         if wanted in labels and str(conv.get("room_id") or "").startswith("!"):
240:             return True
241:     return False

242:
243: def _approval_origin_context(request_item: dict[str, Any]) -> dict[str, Any] | None:
244:     room_id = str(request_item.get("matrix_room_id") or "").strip()
245:     if not room_id.startswith("!"):
246:         return None
247:     event_id = str(request_item.get("matrix_event_id") or "").strip()
248:     context = {
249:         "originating_channel": "matrix",
250:         "originating_to": f"room:{room_id}",
251:         "reply_to_event_id": event_id,
252:     }
253:     return {k: v for k, v in context.items() if v}
254:
255:
256: def _create_acp_wakeup_timer(
257:     *, request_item: dict[str, Any], event: str, message: str, delay_seconds: int = 5
258: ) -> dict[str, Any] | None:
259:     raw_label = str(request_item.get("session_label") or "").strip()
260:     if not raw_label:
261:         raw_label = _binding_label_for_room(str(request_item.get("matrix_room_id") or ""))
262:     if (
263:         not raw_label
264:         and str(request_item.get("tool") or "").strip()
265:         == "persistent-chat-approved-patch"
266:     ):
267:         raw_label = "persistent-chat"
268:     label = _resolve_timer_binding(raw_label)
269:     if not label or not _binding_exists(label, "matrix"):
270:         return None
271:     due = datetime.now(timezone.utc) + timedelta(seconds=max(0, int(delay_seconds)))
272:     timer = {
273:         "id": f"tmr-{uuid.uuid4().hex[:10]}",
274:         "type": "acp_wakeup",
275:         "status": "pending",
276:         "created_at": _utc_timestamp(),
277:         "updated_at": _utc_timestamp(),
278:         "due_at": _utc_timestamp(due),
279:         "message": message[:3900],
280:         "channel": "matrix",
281:         "binding_label": label,
282:         "session_label": label,
283:         "payload": {
284:             "timeout_seconds": 900,
285:             "approval_request_id": request_item.get("id"),
286:             "event": event,
287:             **(
288:                 {"origin_context": origin_context}
289:                 if (origin_context := _approval_origin_context(request_item))
290:                 else {}
291:             ),
292:         },
293:         "reschedule_seconds": 0,
294:         "check_count": 0,
295:         "max_checks": 1,
296:     }
297:     TIMER_STATE_DIR.mkdir(parents=True, exist_ok=True)
298:     with TIMER_LOCK_FILE.open("a+") as lock:
299:         fcntl.flock(lock.fileno(), fcntl.LOCK_EX)
300:         timers = _load(TIMERS_FILE, [])
301:         if not isinstance(timers, list):
302:             timers = []
303:         timers.append(timer)
304:         _save(TIMERS_FILE, timers)
305:         fcntl.flock(lock.fileno(), fcntl.LOCK_UN)
306:     return timer
307:
308:
309: def _cancel_timer(timer_id: str, reason: str) -> bool:
310:     clean = str(timer_id or "").strip()
311:     if not clean:
312:         return False
313:     TIMER_STATE_DIR.mkdir(parents=True, exist_ok=True)
314:     with TIMER_LOCK_FILE.open("a+") as lock:
315:         fcntl.flock(lock.fileno(), fcntl.LOCK_EX)
316:         timers = _load(TIMERS_FILE, [])
317:         if not isinstance(timers, list):
318:             timers = []
319:         changed = False
320:         for timer in timers:
321:             if not isinstance(timer, dict) or timer.get("id") != clean:
322:                 continue
323:             if timer.get("status") == "pending":
324:                 timer["status"] = "cancelled"
325:                 timer["cancelled_at"] = _utc_timestamp()
326:                 timer["cancel_reason"] = reason[:300]
327:                 timer["updated_at"] = _utc_timestamp()
328:                 changed = True
329:             break
330:         if changed:
331:             _save(TIMERS_FILE, timers)
332:         fcntl.flock(lock.fileno(), fcntl.LOCK_UN)
333:     return changed
334:
335:
336: def _approval_wakeup_message(
337:     request_item: dict[str, Any], event: str, reviewer: str = ""
338: ) -> str:
339:     tool = str(request_item.get("tool") or "")
340:     wrapper_note = (
341:         f" If approved, continue by running the exact persistent-chat-approved-patch wrapper command with --approval-id {request_item.get('id')}. Do not call approval_policy_check first; the wrapper validates and consumes the approval itself."
342:         if tool in SELF_CONSUMING_APPROVAL_TOOLS
343:         else " If approved, first verify approval_policy_check(...) unless the approved wrapper explicitly says it self-validates."
344:     )
345:     return (
346:         "Approval follow-up wakeup.\n"
347:         f"Request id: {request_item.get('id')}\n"
348:         f"Event: {event}\n"
349:         f"Reviewer: {reviewer or request_item.get('reviewer') or 'unknown'}\n"
350:         f"Tool: {request_item.get('tool', '')}\n"
351:         f"Action: {request_item.get('action', '')}\n"
352:         f"Scope: {request_item.get('scope', '')}\n\n"
353:         "Act now in the original user-facing session."
354:         f"{wrapper_note}"
355:         " Report success or any tool error plainly. If denied or still pending/expired, stop the risky action, explain the state, and propose a safe alternative. Do not wait for the user to say done."
356:     )
357:
358:
359: def _match_pattern(value: str, pattern: str | None) -> bool:
360:     if not pattern or pattern == "*":
361:         return True

362:     return fnmatch.fnmatchcase(value or "", pattern)
363:
364:
365: def _matrix_env() -> tuple[str, str]:
366:     homeserver = os.environ.get("MATRIX_HOMESERVER_URL", "").strip().rstrip("/")
367:     token = os.environ.get("MATRIX_ACCESS_TOKEN", "").strip()
368:     if not homeserver or not token:
369:         raise RuntimeError("MATRIX_HOMESERVER_URL and MATRIX_ACCESS_TOKEN are not set")
370:     return homeserver, token
371:
372:
373: def _matrix_room_id(room: str) -> str:
374:     wanted = str(room or "approvals").strip()
375:     if wanted.startswith("!"):
376:         return wanted
377:     cfg = json.loads(CONFIG_FILE.read_text(encoding="utf-8"))
378:     conversations = cfg.get("conversations") if isinstance(cfg.get("conversations"), dict) else {}
379:     wanted_lower = wanted.lower()
380:     for key, conv in conversations.items():
381:         if not isinstance(conv, dict):
382:             continue
383:         labels = {str(key).lower(), str(conv.get("agent") or "").lower()}
384:         if wanted_lower in labels:
385:             room_id = str(conv.get("room_id") or "")
386:             if room_id.startswith("!"):
387:                 return room_id
388:     # Matrix-lite currently prefers same-room approvals.  Older prompt/tool
389:     # examples call room="approvals", but no dedicated approvals room may be
390:     # configured.  Fall back to the persistent-chat room instead of recording a
391:     # request that Gleb never sees.
392:     if wanted_lower in {"approvals", "approval", "default", ""}:
393:         for key in ("persistent:main", "matrix-lite:phase1"):
394:             conv = conversations.get(key) if isinstance(conversations, dict) else None
395:             if isinstance(conv, dict):
396:                 room_id = str(conv.get("room_id") or "")
397:                 if room_id.startswith("!"):
398:                     return room_id
399:     raise RuntimeError(f"Matrix room binding not found: {room}")
400:
401: def _redact_sensitive(value: str, limit: int = 1000) -> str:
402:     text = str(value or "")[:limit]
403:     patterns = [
404:         r"(?i)(bearer\s+)[A-Za-z0-9._~+/=-]{12,}",
405:         r"(?i)((?:api[_-]?key|token|secret|password|passwd|pwd)\s*[:=]\s*)[^\s,;]{6,}",
406:         r"\b(sk-[A-Za-z0-9_-]{12,})\b",
407:         r"\b(gh[pousr]_[A-Za-z0-9_]{12,})\b",
408:         r"\b(xox[baprs]-[A-Za-z0-9-]{12,})\b",
409:     ]
410:     for pattern in patterns:
411:         text = re.sub(
412:             pattern,
413:             lambda match: (
414:                 match.group(1) + "<redacted>" if match.lastindex else "<redacted>"
415:             ),
416:             text,
417:         )
418:     return text
419:
420:
421: def _send_matrix_notice(room_id: str, text: str) -> dict[str, Any]:
422:     homeserver, token = _matrix_env()
423:     event_type = "m.room.message"
424:     txn_id = f"matrix-lite-approval-{int(time.time() * 1000)}"
425:     path = f"/rooms/{parse.quote(room_id, safe='')}/send/{event_type}/{txn_id}"
426:     lines = text[:3900].splitlines()
427:     title = escape(lines[0]) if lines else "Approval requested"
428:     rest = "
".join(escape(line) for line in lines[1:] if line.strip())
429:     formatted = f"
{title}"
430:     if rest:
431:         formatted += f"
{rest}"
432:     formatted += "
"
433:     body = json.dumps(
434:         {
435:             "msgtype": "m.notice",
436:             "body": text[:3900],
437:             "format": "org.matrix.custom.html",
438:             "formatted_body": formatted,
439:         }
440:     ).encode("utf-8")
441:     req = request.Request(
442:         f"{homeserver}/_matrix/client/v3{path}",
443:         data=body,
444:         method="PUT",
445:         headers={
446:             "Authorization": f"Bearer {token}",
447:             "Content-Type": "application/json",
448:         },
449:     )
450:     try:
451:         with request.urlopen(req, timeout=30) as response:
452:             result = json.loads(response.read().decode("utf-8"))
453:     except HTTPError as exc:
454:         detail = exc.read().decode("utf-8", errors="replace")
455:         raise RuntimeError(f"Matrix API failed: HTTP {exc.code}: {detail}") from exc
456:     except URLError as exc:
457:         raise RuntimeError(f"Matrix API failed: {exc.reason}") from exc
458:     return {"ok": True, "room_id": room_id, "event_id": result.get("event_id")}
459:
460:
461: def _approval_message(item: dict[str, Any], allow_always_hint: bool) -> str:
462:     tool = str(item.get("tool") or "")
463:     action = str(item.get("action") or "")
464:     action_label = "Command" if tool in {"bash", "python"} else "Action"
465:     lines = [
466:         f"Approval requested: {item['id']}",
467:         str(item.get("summary") or "")[:1000],
468:         f"Tool: {tool}",
469:         f"Scope: {item.get('scope', '')}",
470:         f"Session: {item.get('session_label', '')}",
471:     ]
472:     if action:
473:         lines.append(f"{action_label}:")
474:         lines.append(action)
475:     lines.extend(
476:         [
477:             "Grant with approval_policy_grant, or deny with approval_policy_deny.",
478:             f'Allow once: approval_policy_grant(request_id="{item["id"]}", mode="once")',
479:         ]
480:     )
481:     if allow_always_hint:

No thanks. Run an explore subagent: find where native subagent profiles
     are defined and report the exact file/function.