synix-agent-mesh 0.1.0__tar.gz

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.
@@ -0,0 +1,68 @@
1
+ You are a senior engineer reviewing a pull request for synix-agent-mesh, an
2
+ orchestration layer that composes synix (build system for agent memory), synix
3
+ mesh (cross-device session sync), and MCP into a single CLI tool. You have
4
+ NEVER seen the codebase before. You are reviewing this PR using only the README
5
+ and the diff.
6
+
7
+ ## Your context
8
+
9
+ You have been given two documents:
10
+ 1. **README.md** — the full project documentation including architecture, CLI
11
+ reference, configuration, and multi-machine setup
12
+ 2. **The PR diff**
13
+
14
+ This is an orchestration/glue project — it is intentionally thin (~500 lines)
15
+ and delegates heavy lifting to synix core, synix mesh, and synix MCP. The CLI
16
+ is called `sam`.
17
+
18
+ ## What to evaluate
19
+
20
+ ### Orchestration correctness
21
+ Does this change correctly compose the underlying synix components? Does it
22
+ respect the separation between synix-agent-mesh (orchestration) and synix
23
+ (core build system)? Flag any change that duplicates functionality that
24
+ belongs in synix core.
25
+
26
+ ### Configuration coherence
27
+ The project uses a single `agent-mesh.toml` config file. Does this change
28
+ maintain config consistency? Are new settings documented? Do environment
29
+ variable overrides work correctly?
30
+
31
+ ### Multi-machine concerns
32
+ The system runs across multiple machines (server, client, MCP router). Does
33
+ this change work correctly in all roles? Are there assumptions about being
34
+ the server that would break on client nodes? Are ports and hostnames
35
+ configurable?
36
+
37
+ ### CLI ergonomics
38
+ Is the CLI interface intuitive? Are error messages helpful? Does the output
39
+ formatting make sense? Would a user running `sam serve` or `sam join` for the
40
+ first time understand what's happening?
41
+
42
+ ### Async and process management
43
+ The server runs mesh server + MCP HTTP + viewer + local client watcher in a
44
+ single process using asyncio + threading. Does this change handle shutdown
45
+ correctly? Are there potential deadlocks or resource leaks?
46
+
47
+ ### Legibility
48
+ Can you understand what the code does from the diff alone? Are names clear?
49
+ Would a new contributor be confused by anything?
50
+
51
+ ### Test coverage
52
+ Does the diff include tests? Do the tests cover the happy path AND edge cases?
53
+ Flag any behavioral changes that appear untested.
54
+
55
+ ## Output format
56
+
57
+ Write a structured review with these sections:
58
+
59
+ **Summary** — What this PR does in 2-3 sentences.
60
+
61
+ **Observations** — Numbered list of specific findings. Each should reference a
62
+ file or code pattern from the diff. Categorize each as: [concern], [question],
63
+ [nit], or [positive].
64
+
65
+ **Verdict** — One sentence: does this PR seem like a good incremental step for
66
+ the project?
67
+
68
+ Keep the total review under 600 words. Be direct. Skip generic praise.
@@ -0,0 +1,73 @@
1
+ You are a skeptical systems architect doing a design review of a pull request.
2
+ Your job is to find problems. You are not here to be encouraging. You are here
3
+ to protect the project from bad decisions.
4
+
5
+ The project is synix-agent-mesh — an orchestration layer that wires together
6
+ synix (build system for agent memory), synix mesh (cross-device session sync),
7
+ and MCP into a single CLI. It is pre-1.0 and under active development. You
8
+ have NEVER seen the codebase. You are working from documentation and the diff
9
+ only.
10
+
11
+ ## Your context
12
+
13
+ You have two documents:
14
+ 1. **README.md** — full project documentation
15
+ 2. **The PR diff**
16
+
17
+ ## Your mandate
18
+
19
+ Hunt for problems. Specifically:
20
+
21
+ ### One-way doors
22
+ Decisions that are hard or impossible to reverse once shipped. Examples:
23
+ - CLI commands or flags that users will depend on
24
+ - Configuration key names that get documented and relied upon
25
+ - Port numbers or protocol choices that become part of deployment guides
26
+ - Mesh protocol changes that affect multi-machine compatibility
27
+
28
+ For each one-way door: name it, explain why it is hard to reverse, and suggest
29
+ what would need to be true for it to be safe to merge.
30
+
31
+ ### Orchestration failures
32
+ - Error handling gaps when synix core APIs change or fail
33
+ - Assumptions about synix internals that could break on version upgrades
34
+ - Missing graceful degradation (e.g., viewer unavailable, mesh unreachable)
35
+ - Process lifecycle issues (zombie processes, unhandled signals, port conflicts)
36
+
37
+ ### Multi-machine risks
38
+ - Changes that work on the server but break on client nodes
39
+ - Hardcoded hostnames, paths, or ports that should be configurable
40
+ - Token or credential handling issues
41
+ - Network timeout or retry gaps
42
+
43
+ ### What is NOT in the diff
44
+ - Missing tests for behavioral changes
45
+ - Missing error handling for plausible failure modes
46
+ - Missing config documentation when new settings are added
47
+ - Regression risks: does this change something that other code depends on?
48
+
49
+ ### Hidden complexity
50
+ - Changes that look simple but have non-obvious downstream effects
51
+ - Implicit dependencies between the server, client, and MCP components
52
+ - Assumptions about execution order or environment state
53
+ - Magic numbers, hardcoded paths, or configuration that should be parameterized
54
+
55
+ ## Output format
56
+
57
+ **Threat assessment** — One sentence: how risky is this PR?
58
+
59
+ **One-way doors** — Numbered list. If none found, say "None identified."
60
+
61
+ **Findings** — Numbered list of problems found. Each must:
62
+ 1. Name the specific file and code pattern
63
+ 2. Explain the failure mode or risk
64
+ 3. Rate severity: [critical], [warning], or [minor]
65
+
66
+ **Missing** — What you expected to see but did not. Be specific.
67
+
68
+ **Verdict** — Ship it, ship with fixes, or block. One sentence with reasoning.
69
+
70
+ Be blunt. If the PR looks clean, say so in two sentences and move on. Do not
71
+ manufacture concerns to fill space. But do not go easy either.
72
+
73
+ Keep the total review under 700 words.
@@ -0,0 +1,263 @@
1
+ """Blind PR review — posts an LLM review using only docs + diff as context."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import sys
7
+ from datetime import UTC, datetime
8
+ from pathlib import Path
9
+
10
+ import requests
11
+
12
+ # ---------------------------------------------------------------------------
13
+ # Configuration
14
+ # ---------------------------------------------------------------------------
15
+
16
+ MAX_DIFF_LINES = 10_000
17
+ LLM_TIMEOUT = 120 # seconds
18
+
19
+ CLAUDE_MODEL = "claude-opus-4-6"
20
+ OPENAI_MODEL = "gpt-5.4"
21
+
22
+ PROMPTS_DIR = Path(__file__).resolve().parent.parent / "prompts"
23
+
24
+ # ---------------------------------------------------------------------------
25
+ # Context gathering
26
+ # ---------------------------------------------------------------------------
27
+
28
+
29
+ def fetch_diff(repo: str, pr_number: str, token: str) -> str:
30
+ """Fetch PR diff via GitHub API, falling back to git diff for large PRs."""
31
+ url = f"https://api.github.com/repos/{repo}/pulls/{pr_number}"
32
+ headers = {
33
+ "Authorization": f"Bearer {token}",
34
+ "Accept": "application/vnd.github.v3.diff",
35
+ }
36
+ resp = requests.get(url, headers=headers, timeout=30)
37
+ if resp.status_code == 406:
38
+ # Diff too large for GitHub API — fall back to local git diff
39
+ print("Diff too large for API (406), falling back to git diff...")
40
+ return _git_diff_fallback(repo, pr_number, token)
41
+ resp.raise_for_status()
42
+ return resp.text
43
+
44
+
45
+ def _git_diff_fallback(repo: str, pr_number: str, token: str) -> str:
46
+ """Compute diff locally using git when the API can't generate it."""
47
+ import subprocess
48
+
49
+ # Fetch PR metadata to get base/head refs
50
+ url = f"https://api.github.com/repos/{repo}/pulls/{pr_number}"
51
+ headers = {
52
+ "Authorization": f"Bearer {token}",
53
+ "Accept": "application/vnd.github.v3+json",
54
+ }
55
+ resp = requests.get(url, headers=headers, timeout=15)
56
+ resp.raise_for_status()
57
+ pr_data = resp.json()
58
+ base_sha = pr_data["base"]["sha"]
59
+ head_sha = pr_data["head"]["sha"]
60
+
61
+ result = subprocess.run(
62
+ ["git", "diff", f"{base_sha}...{head_sha}"],
63
+ capture_output=True,
64
+ text=True,
65
+ timeout=30,
66
+ )
67
+ if result.returncode != 0:
68
+ raise RuntimeError(f"git diff failed: {result.stderr}")
69
+ return result.stdout
70
+
71
+
72
+ def truncate_diff(diff: str) -> tuple[str, int, int]:
73
+ """Truncate diff to MAX_DIFF_LINES. Returns (diff, shown_lines, total_lines)."""
74
+ lines = diff.splitlines()
75
+ total = len(lines)
76
+ if total <= MAX_DIFF_LINES:
77
+ return diff, total, total
78
+ truncated = "\n".join(lines[:MAX_DIFF_LINES])
79
+ truncated += f"\n\n... [diff truncated: showing first {MAX_DIFF_LINES:,} of {total:,} lines]"
80
+ return truncated, MAX_DIFF_LINES, total
81
+
82
+
83
+ def load_prompt(provider: str) -> str:
84
+ """Load the review prompt for the given provider."""
85
+ prompt_file = PROMPTS_DIR / f"{provider}_review.md"
86
+ return prompt_file.read_text()
87
+
88
+
89
+ def read_file(name: str) -> str:
90
+ """Read a file from the workspace."""
91
+ workspace = Path(os.environ.get("GITHUB_WORKSPACE", "."))
92
+ path = workspace / name
93
+ if path.exists():
94
+ return path.read_text()
95
+ return f"({name} not found)"
96
+
97
+
98
+ # ---------------------------------------------------------------------------
99
+ # LLM calls
100
+ # ---------------------------------------------------------------------------
101
+
102
+
103
+ def build_user_message(readme: str, diff: str) -> str:
104
+ """Compose the user message with context sections."""
105
+ return f"""## README.md
106
+
107
+ {readme}
108
+
109
+ ---
110
+
111
+ ## PR Diff
112
+
113
+ ```diff
114
+ {diff}
115
+ ```"""
116
+
117
+
118
+ def review_claude(system_prompt: str, user_message: str) -> tuple[str, str]:
119
+ """Call Claude API with extended thinking. Returns (review_text, model_name)."""
120
+ import anthropic
121
+
122
+ client = anthropic.Anthropic()
123
+ response = client.messages.create(
124
+ model=CLAUDE_MODEL,
125
+ max_tokens=16000,
126
+ temperature=1, # required for extended thinking
127
+ thinking={
128
+ "type": "enabled",
129
+ "budget_tokens": 10000,
130
+ },
131
+ system=system_prompt,
132
+ messages=[{"role": "user", "content": user_message}],
133
+ )
134
+ # Extract the text block (skip thinking blocks)
135
+ for block in response.content:
136
+ if block.type == "text":
137
+ return block.text, CLAUDE_MODEL
138
+ return "(no text in response)", CLAUDE_MODEL
139
+
140
+
141
+ def review_openai(system_prompt: str, user_message: str) -> tuple[str, str]:
142
+ """Call OpenAI API. Returns (review_text, model_name)."""
143
+ import openai
144
+
145
+ client = openai.OpenAI()
146
+ response = client.chat.completions.create(
147
+ model=OPENAI_MODEL,
148
+ messages=[
149
+ {"role": "developer", "content": system_prompt},
150
+ {"role": "user", "content": user_message},
151
+ ],
152
+ )
153
+ return response.choices[0].message.content, OPENAI_MODEL
154
+
155
+
156
+ # ---------------------------------------------------------------------------
157
+ # GitHub interaction
158
+ # ---------------------------------------------------------------------------
159
+
160
+
161
+ def post_comment(repo: str, pr_number: str, token: str, body: str) -> None:
162
+ """Post a comment on the PR via GitHub API."""
163
+ url = f"https://api.github.com/repos/{repo}/issues/{pr_number}/comments"
164
+ headers = {
165
+ "Authorization": f"Bearer {token}",
166
+ "Accept": "application/vnd.github.v3+json",
167
+ }
168
+ resp = requests.post(url, headers=headers, json={"body": body}, timeout=15)
169
+ resp.raise_for_status()
170
+
171
+
172
+ def format_comment(
173
+ review: str,
174
+ provider: str,
175
+ model: str,
176
+ diff_shown: int,
177
+ diff_total: int,
178
+ prompt_file: str,
179
+ ) -> str:
180
+ """Format the review as a PR comment."""
181
+ timestamp = datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ")
182
+
183
+ if provider == "claude":
184
+ header = "**Architectural Review** — Claude Opus | Blind review (docs + diff only)"
185
+ else:
186
+ header = "**Red Team Review** — OpenAI GPT-5.4 | Adversarial review (docs + diff only)"
187
+
188
+ diff_note = f"{diff_shown:,} lines"
189
+ if diff_shown < diff_total:
190
+ diff_note += f" (of {diff_total:,} total — truncated)"
191
+
192
+ return f"""> [!NOTE]
193
+ > {header}
194
+
195
+ {review}
196
+
197
+ <details>
198
+ <summary>Review parameters</summary>
199
+
200
+ - **Model**: `{model}`
201
+ - **Context**: README.md, PR diff
202
+ - **Diff size**: {diff_note}
203
+ - **Prompt**: `{prompt_file}`
204
+ - **Timestamp**: {timestamp}
205
+
206
+ </details>
207
+ """
208
+
209
+
210
+ # ---------------------------------------------------------------------------
211
+ # Main
212
+ # ---------------------------------------------------------------------------
213
+
214
+
215
+ def main() -> None:
216
+ provider = os.environ["PROVIDER"] # "claude" or "openai"
217
+ token = os.environ["GITHUB_TOKEN"]
218
+ pr_number = os.environ["PR_NUMBER"]
219
+ repo = os.environ["REPO"]
220
+
221
+ # 1. Gather context
222
+ print(f"Fetching diff for {repo}#{pr_number}...")
223
+ raw_diff = fetch_diff(repo, pr_number, token)
224
+ diff, diff_shown, diff_total = truncate_diff(raw_diff)
225
+ print(f"Diff: {diff_total:,} lines ({diff_shown:,} shown)")
226
+
227
+ readme = read_file("README.md")
228
+
229
+ # 2. Build prompt and call LLM
230
+ system_prompt = load_prompt(provider)
231
+ user_message = build_user_message(readme, diff)
232
+ prompt_file = f".github/prompts/{provider}_review.md"
233
+
234
+ print(f"Calling {provider} API ({CLAUDE_MODEL if provider == 'claude' else OPENAI_MODEL})...")
235
+ try:
236
+ if provider == "claude":
237
+ review, model = review_claude(system_prompt, user_message)
238
+ elif provider == "openai":
239
+ review, model = review_openai(system_prompt, user_message)
240
+ else:
241
+ print(f"Unknown provider: {provider}", file=sys.stderr)
242
+ sys.exit(1)
243
+ except Exception as exc:
244
+ error_type = type(exc).__name__
245
+ fail_body = (
246
+ f"> [!WARNING]\n"
247
+ f"> **AI Review failed** — {provider} | `{error_type}`\n\n"
248
+ f"The {provider} blind review could not be completed. "
249
+ f"This does not affect the PR.\n"
250
+ )
251
+ print(f"LLM call failed ({error_type}): {exc}", file=sys.stderr)
252
+ post_comment(repo, pr_number, token, fail_body)
253
+ sys.exit(0) # don't red-X the PR
254
+
255
+ # 3. Post review comment
256
+ print(f"Posting review ({len(review)} chars)...")
257
+ body = format_comment(review, provider, model, diff_shown, diff_total, prompt_file)
258
+ post_comment(repo, pr_number, token, body)
259
+ print("Done.")
260
+
261
+
262
+ if __name__ == "__main__":
263
+ main()
@@ -0,0 +1,87 @@
1
+ name: Blind PR Review
2
+
3
+ on:
4
+ pull_request_target:
5
+ types: [opened, synchronize]
6
+
7
+ env:
8
+ APPROVED_CONTRIBUTORS: '["marklubin"]'
9
+
10
+ jobs:
11
+ gate:
12
+ runs-on: ubuntu-latest
13
+ outputs:
14
+ approved: ${{ steps.check.outputs.approved }}
15
+ steps:
16
+ - id: check
17
+ run: |
18
+ ACTOR="${{ github.actor }}"
19
+ if echo '${{ env.APPROVED_CONTRIBUTORS }}' | jq -e ". | index(\"$ACTOR\")" > /dev/null 2>&1; then
20
+ echo "approved=true" >> "$GITHUB_OUTPUT"
21
+ echo "::notice::Blind review approved for $ACTOR"
22
+ else
23
+ echo "approved=false" >> "$GITHUB_OUTPUT"
24
+ echo "::notice::Skipping blind review — $ACTOR is not in approved list"
25
+ fi
26
+
27
+ review-claude:
28
+ needs: gate
29
+ if: needs.gate.outputs.approved == 'true'
30
+ runs-on: ubuntu-latest
31
+ permissions:
32
+ pull-requests: write
33
+ steps:
34
+ - uses: actions/checkout@v4
35
+ with:
36
+ ref: ${{ github.event.repository.default_branch }}
37
+ sparse-checkout: |
38
+ .github/scripts/blind_review.py
39
+ .github/prompts/claude_review.md
40
+ .github/prompts/openai_review.md
41
+ README.md
42
+ sparse-checkout-cone-mode: false
43
+
44
+ - uses: actions/setup-python@v5
45
+ with:
46
+ python-version: "3.12"
47
+
48
+ - run: pip install anthropic requests
49
+
50
+ - run: python .github/scripts/blind_review.py
51
+ env:
52
+ PROVIDER: claude
53
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
54
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
55
+ PR_NUMBER: ${{ github.event.pull_request.number }}
56
+ REPO: ${{ github.repository }}
57
+
58
+ review-openai:
59
+ needs: gate
60
+ if: needs.gate.outputs.approved == 'true'
61
+ runs-on: ubuntu-latest
62
+ permissions:
63
+ pull-requests: write
64
+ steps:
65
+ - uses: actions/checkout@v4
66
+ with:
67
+ ref: ${{ github.event.repository.default_branch }}
68
+ sparse-checkout: |
69
+ .github/scripts/blind_review.py
70
+ .github/prompts/claude_review.md
71
+ .github/prompts/openai_review.md
72
+ README.md
73
+ sparse-checkout-cone-mode: false
74
+
75
+ - uses: actions/setup-python@v5
76
+ with:
77
+ python-version: "3.12"
78
+
79
+ - run: pip install openai requests
80
+
81
+ - run: python .github/scripts/blind_review.py
82
+ env:
83
+ PROVIDER: openai
84
+ OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
85
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
86
+ PR_NUMBER: ${{ github.event.pull_request.number }}
87
+ REPO: ${{ github.repository }}
@@ -0,0 +1,22 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: ["*"]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.11", "3.12", "3.13"]
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: astral-sh/setup-uv@v4
18
+ with:
19
+ python-version: ${{ matrix.python-version }}
20
+ - run: uv sync
21
+ - run: uv run ruff check .
22
+ - run: uv run pytest tests/ -v
@@ -0,0 +1,53 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ permissions:
8
+ id-token: write
9
+
10
+ jobs:
11
+ test:
12
+ runs-on: ubuntu-latest
13
+ strategy:
14
+ matrix:
15
+ python-version: ["3.11", "3.12", "3.13"]
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - uses: astral-sh/setup-uv@v4
19
+ with:
20
+ python-version: ${{ matrix.python-version }}
21
+ - run: uv sync
22
+ - run: uv run ruff check .
23
+ - run: uv run pytest tests/ -v
24
+
25
+ publish:
26
+ needs: [test]
27
+ runs-on: ubuntu-latest
28
+ environment: pypi
29
+ permissions:
30
+ id-token: write
31
+ steps:
32
+ - uses: actions/checkout@v4
33
+ - uses: astral-sh/setup-uv@v4
34
+ with:
35
+ python-version: "3.12"
36
+ - run: uv build
37
+ - uses: pypa/gh-action-pypi-publish@release/v1
38
+
39
+ smoke-test:
40
+ needs: publish
41
+ runs-on: ubuntu-latest
42
+ steps:
43
+ - uses: astral-sh/setup-uv@v4
44
+ with:
45
+ python-version: "3.12"
46
+ - name: Wait for PyPI propagation
47
+ run: sleep 30
48
+ - name: Install from PyPI
49
+ run: uv tool install synix-agent-mesh
50
+ - name: Verify CLI
51
+ run: |
52
+ sam --help
53
+ sam --version
@@ -0,0 +1,11 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ .synix/
5
+ .pytest_cache/
6
+ *.egg-info/
7
+ dist/
8
+ build/
9
+ uv.lock
10
+ uv.toml
11
+ agent-mesh.toml
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mark Lubin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.