@synapsor/runner 0.1.0-alpha.9 → 0.1.1
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.
- package/CHANGELOG.md +189 -0
- package/README.md +949 -164
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/runner.mjs +2982 -238
- package/docs/README.md +90 -15
- package/docs/app-owned-executors.md +38 -0
- package/docs/capability-authoring.md +265 -0
- package/docs/cloud-mode.md +24 -0
- package/docs/current-scope.md +29 -0
- package/docs/dependency-license-inventory.md +35 -0
- package/docs/doctor.md +98 -0
- package/docs/getting-started-own-database.md +131 -46
- package/docs/handler-helper.md +228 -0
- package/docs/http-mcp.md +85 -17
- package/docs/licensing.md +36 -0
- package/docs/local-mode.md +44 -25
- package/docs/mcp-audit.md +8 -8
- package/docs/mcp-client-setup.md +59 -21
- package/docs/openai-agents-sdk.md +57 -0
- package/docs/recipes.md +6 -6
- package/docs/release-notes.md +348 -0
- package/docs/release-policy.md +125 -0
- package/docs/result-envelope-v2.md +151 -0
- package/docs/rfcs/001-result-envelope-v2.md +143 -0
- package/docs/rfcs/002-app-owned-handler-helper.md +161 -0
- package/docs/rfcs/003-integrator-feedback-teardown.md +97 -0
- package/docs/store-lifecycle.md +83 -0
- package/docs/troubleshooting-first-run.md +6 -6
- package/docs/use-your-own-database.md +18 -0
- package/docs/writeback-executors.md +92 -1
- package/examples/app-owned-writeback/README.md +128 -0
- package/examples/app-owned-writeback/business-actions.md +221 -0
- package/examples/app-owned-writeback/command-handler.mjs +55 -0
- package/examples/app-owned-writeback/node-fastify-handler.mjs +64 -0
- package/examples/app-owned-writeback/python-fastapi-handler.py +66 -0
- package/examples/claude-desktop-postgres/Makefile +6 -0
- package/examples/claude-desktop-postgres/README.md +40 -0
- package/examples/cursor-postgres/Makefile +6 -0
- package/examples/cursor-postgres/README.md +30 -0
- package/examples/mcp-postgres-billing-app-handler/README.md +94 -0
- package/examples/mcp-postgres-billing-app-handler/app-handler.mjs +123 -0
- package/examples/mcp-postgres-billing-app-handler/docker-compose.yml +13 -0
- package/examples/mcp-postgres-billing-app-handler/schema.sql +59 -0
- package/examples/mcp-postgres-billing-app-handler/scripts/run-demo.sh +100 -0
- package/examples/mcp-postgres-billing-app-handler/seed.sql +39 -0
- package/examples/mcp-postgres-billing-app-handler/synapsor-handler.mjs +437 -0
- package/examples/mcp-postgres-billing-app-handler/synapsor.runner.json +158 -0
- package/examples/mysql-refund-agent/Makefile +4 -0
- package/examples/mysql-refund-agent/README.md +36 -0
- package/examples/openai-agents-http/Makefile +6 -0
- package/examples/openai-agents-http/README.md +33 -12
- package/examples/openai-agents-http/agent.py +29 -65
- package/examples/openai-agents-stdio/Makefile +6 -0
- package/examples/openai-agents-stdio/README.md +24 -6
- package/examples/openai-agents-stdio/agent.py +4 -2
- package/examples/raw-sql-vs-synapsor/Makefile +11 -0
- package/examples/raw-sql-vs-synapsor/README.md +41 -0
- package/examples/reference-support-billing-app/README.md +16 -16
- package/examples/reference-support-billing-app/mcp-client.generic.json +1 -1
- package/examples/support-billing-agent/Makefile +19 -0
- package/examples/support-billing-agent/README.md +89 -0
- package/examples/support-billing-agent/app/README.md +13 -0
- package/examples/support-billing-agent/db/schema.sql +91 -0
- package/examples/support-billing-agent/db/seed.sql +43 -0
- package/examples/support-billing-agent/docker-compose.yml +13 -0
- package/examples/support-billing-agent/scripts/run-demo.sh +15 -0
- package/examples/support-billing-agent/synapsor.runner.json +233 -0
- package/fixtures/benchmark/mcp-efficiency.json +53 -0
- package/fixtures/benchmark/mcp-efficiency.txt +25 -0
- package/fixtures/protocol/MANIFEST.json +54 -0
- package/fixtures/protocol/change-set.late-fee-waiver.v1.json +72 -0
- package/fixtures/protocol/execution-receipt.applied.v1.json +14 -0
- package/fixtures/protocol/execution-receipt.conflict.v1.json +15 -0
- package/fixtures/protocol/runner-registration.v1.json +22 -0
- package/fixtures/protocol/writeback-job.late-fee-waiver.v1.json +44 -0
- package/package.json +27 -4
- package/schemas/change-set.v1.schema.json +140 -0
- package/schemas/execution-receipt.v1.schema.json +34 -0
- package/schemas/onboarding-selection.v1.schema.json +132 -0
- package/schemas/runner-registration.v1.schema.json +48 -0
- package/schemas/synapsor.app-handler-receipt.v1.json +39 -0
- package/schemas/synapsor.app-handler-request.v1.json +119 -0
- package/schemas/synapsor.runner.schema.json +415 -0
- package/schemas/writeback-job.v1.schema.json +121 -0
|
@@ -1,21 +1,39 @@
|
|
|
1
|
-
# OpenAI Agents SDK + Synapsor Runner
|
|
1
|
+
# OpenAI Agents SDK + Synapsor Runner Streamable HTTP MCP
|
|
2
2
|
|
|
3
|
-
This example shows an OpenAI Agents SDK app
|
|
4
|
-
Runner HTTP MCP
|
|
3
|
+
This example shows an OpenAI Agents SDK app connecting to a long-running
|
|
4
|
+
Synapsor Runner Streamable HTTP MCP server.
|
|
5
5
|
|
|
6
6
|
Use HTTP when your agent runs as an app/server and should connect to Runner
|
|
7
7
|
over a local/private network endpoint instead of launching a stdio child
|
|
8
8
|
process.
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
This example uses `synapsor-runner mcp serve-streamable-http`, the
|
|
11
|
+
spec-compatible HTTP MCP transport with `initialize` and session behavior. Use
|
|
12
|
+
`synapsor-runner mcp serve-http` only when you intentionally want the smaller
|
|
13
|
+
JSON-RPC bridge and an app-owned wrapper.
|
|
14
|
+
|
|
15
|
+
OpenAI function names cannot contain dots, so start Runner with
|
|
16
|
+
`--alias-mode openai`. The model sees aliases such as
|
|
17
|
+
`billing__inspect_invoice`; Runner keeps `billing.inspect_invoice` in MCP
|
|
18
|
+
metadata and maps calls back to the canonical Synapsor capability.
|
|
15
19
|
|
|
16
20
|
The model still sees a semantic action. It does not receive raw SQL, database
|
|
17
21
|
URLs, write credentials, approval tools, or commit tools.
|
|
18
22
|
|
|
23
|
+
## Smoke Check
|
|
24
|
+
|
|
25
|
+
Run this without an OpenAI API key:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
make smoke
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Expected output:
|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
OpenAI Agents Streamable HTTP example smoke passed.
|
|
35
|
+
```
|
|
36
|
+
|
|
19
37
|
## Terminal 1: Start Synapsor Runner HTTP MCP
|
|
20
38
|
|
|
21
39
|
```bash
|
|
@@ -24,10 +42,11 @@ export SYNAPSOR_TENANT_ID="acme"
|
|
|
24
42
|
export SYNAPSOR_PRINCIPAL="openai_agent_demo"
|
|
25
43
|
export SYNAPSOR_RUNNER_HTTP_TOKEN="dev-token"
|
|
26
44
|
|
|
27
|
-
npx -y -p @synapsor/runner
|
|
45
|
+
npx -y -p @synapsor/runner synapsor-runner mcp serve-streamable-http \
|
|
28
46
|
--config ./synapsor.runner.json \
|
|
29
47
|
--store ./.synapsor/local.db \
|
|
30
|
-
--auth-token-env SYNAPSOR_RUNNER_HTTP_TOKEN
|
|
48
|
+
--auth-token-env SYNAPSOR_RUNNER_HTTP_TOKEN \
|
|
49
|
+
--alias-mode openai
|
|
31
50
|
```
|
|
32
51
|
|
|
33
52
|
## Terminal 2: Run The Agent
|
|
@@ -38,7 +57,7 @@ python -m venv .venv
|
|
|
38
57
|
pip install -r requirements.txt
|
|
39
58
|
|
|
40
59
|
export OPENAI_API_KEY="..."
|
|
41
|
-
export SYNAPSOR_RUNNER_HTTP_URL="http://127.0.0.1:
|
|
60
|
+
export SYNAPSOR_RUNNER_HTTP_URL="http://127.0.0.1:8766/mcp"
|
|
42
61
|
export SYNAPSOR_RUNNER_HTTP_TOKEN="dev-token"
|
|
43
62
|
export SYNAPSOR_INVOICE_ID="INV-3001"
|
|
44
63
|
|
|
@@ -47,7 +66,9 @@ python agent.py
|
|
|
47
66
|
|
|
48
67
|
Expected behavior:
|
|
49
68
|
|
|
50
|
-
- the agent calls `
|
|
69
|
+
- the agent calls the OpenAI-safe alias `billing__inspect_invoice` through
|
|
70
|
+
Synapsor HTTP MCP;
|
|
71
|
+
- Runner maps that alias back to canonical `billing.inspect_invoice`;
|
|
51
72
|
- Synapsor applies trusted tenant/principal context from the server process;
|
|
52
73
|
- the response includes scoped data and evidence handles;
|
|
53
74
|
- no SQL/write/approval tool is exposed to the model;
|
|
@@ -1,63 +1,17 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
import json
|
|
3
2
|
import os
|
|
4
3
|
import sys
|
|
5
|
-
import urllib.error
|
|
6
|
-
import urllib.request
|
|
7
4
|
|
|
8
5
|
try:
|
|
9
|
-
from agents import Agent, Runner
|
|
6
|
+
from agents import Agent, Runner
|
|
7
|
+
from agents.mcp import MCPServerStreamableHttp
|
|
10
8
|
except ImportError as exc:
|
|
11
9
|
raise SystemExit(
|
|
12
|
-
"This example requires the OpenAI Agents SDK. "
|
|
10
|
+
"This example requires the OpenAI Agents SDK with Streamable HTTP MCP support. "
|
|
13
11
|
"Install with: pip install -r requirements.txt"
|
|
14
12
|
) from exc
|
|
15
13
|
|
|
16
14
|
|
|
17
|
-
def call_synapsor_http_mcp(method: str, params: dict) -> dict:
|
|
18
|
-
url = os.environ.get("SYNAPSOR_RUNNER_HTTP_URL", "http://127.0.0.1:8765/mcp")
|
|
19
|
-
token = os.environ.get("SYNAPSOR_RUNNER_HTTP_TOKEN")
|
|
20
|
-
if not token:
|
|
21
|
-
raise RuntimeError("SYNAPSOR_RUNNER_HTTP_TOKEN is not set")
|
|
22
|
-
|
|
23
|
-
payload = json.dumps({
|
|
24
|
-
"jsonrpc": "2.0",
|
|
25
|
-
"id": 1,
|
|
26
|
-
"method": method,
|
|
27
|
-
"params": params,
|
|
28
|
-
}).encode("utf-8")
|
|
29
|
-
request = urllib.request.Request(
|
|
30
|
-
url,
|
|
31
|
-
data=payload,
|
|
32
|
-
method="POST",
|
|
33
|
-
headers={
|
|
34
|
-
"authorization": f"Bearer {token}",
|
|
35
|
-
"content-type": "application/json",
|
|
36
|
-
},
|
|
37
|
-
)
|
|
38
|
-
try:
|
|
39
|
-
with urllib.request.urlopen(request, timeout=15) as response:
|
|
40
|
-
body = response.read().decode("utf-8")
|
|
41
|
-
except urllib.error.HTTPError as exc:
|
|
42
|
-
body = exc.read().decode("utf-8")
|
|
43
|
-
raise RuntimeError(f"Synapsor HTTP MCP returned HTTP {exc.code}: {body}") from exc
|
|
44
|
-
|
|
45
|
-
parsed = json.loads(body)
|
|
46
|
-
if "error" in parsed:
|
|
47
|
-
raise RuntimeError(f"Synapsor HTTP MCP error: {parsed['error']}")
|
|
48
|
-
return parsed["result"]
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
@function_tool
|
|
52
|
-
def billing_inspect_invoice(invoice_id: str) -> str:
|
|
53
|
-
"""Inspect one scoped invoice through Synapsor Runner HTTP MCP."""
|
|
54
|
-
result = call_synapsor_http_mcp("tools/call", {
|
|
55
|
-
"name": "billing.inspect_invoice",
|
|
56
|
-
"arguments": {"invoice_id": invoice_id},
|
|
57
|
-
})
|
|
58
|
-
return json.dumps(result.get("structuredContent", result), indent=2)
|
|
59
|
-
|
|
60
|
-
|
|
61
15
|
async def main() -> None:
|
|
62
16
|
required = ["OPENAI_API_KEY", "SYNAPSOR_RUNNER_HTTP_URL", "SYNAPSOR_RUNNER_HTTP_TOKEN"]
|
|
63
17
|
missing = [name for name in required if not os.environ.get(name)]
|
|
@@ -65,22 +19,32 @@ async def main() -> None:
|
|
|
65
19
|
raise SystemExit(f"Missing required environment variables: {', '.join(missing)}")
|
|
66
20
|
|
|
67
21
|
invoice_id = os.environ.get("SYNAPSOR_INVOICE_ID", "INV-3001")
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
22
|
+
mcp_url = os.environ["SYNAPSOR_RUNNER_HTTP_URL"]
|
|
23
|
+
token = os.environ["SYNAPSOR_RUNNER_HTTP_TOKEN"]
|
|
24
|
+
|
|
25
|
+
async with MCPServerStreamableHttp(
|
|
26
|
+
params={
|
|
27
|
+
"url": mcp_url,
|
|
28
|
+
"headers": {"Authorization": f"Bearer {token}"},
|
|
29
|
+
"timeout": 15,
|
|
30
|
+
}
|
|
31
|
+
) as mcp_server:
|
|
32
|
+
agent = Agent(
|
|
33
|
+
name="Synapsor Streamable HTTP MCP demo agent",
|
|
34
|
+
instructions=(
|
|
35
|
+
"Use Synapsor MCP tools to inspect scoped database data. "
|
|
36
|
+
"Do not claim that you can run SQL, approve proposals, or commit writes."
|
|
37
|
+
),
|
|
38
|
+
mcp_servers=[mcp_server],
|
|
39
|
+
)
|
|
40
|
+
result = await Runner.run(
|
|
41
|
+
agent,
|
|
42
|
+
(
|
|
43
|
+
f"Inspect invoice {invoice_id} using Synapsor. "
|
|
44
|
+
"Explain what you saw and whether you have write authority."
|
|
45
|
+
),
|
|
46
|
+
)
|
|
47
|
+
print(result.final_output)
|
|
84
48
|
|
|
85
49
|
|
|
86
50
|
if __name__ == "__main__":
|
|
@@ -4,22 +4,39 @@ This example shows an OpenAI Agents SDK app launching Synapsor Runner as a
|
|
|
4
4
|
local stdio MCP server.
|
|
5
5
|
|
|
6
6
|
Use stdio when the agent process can start the MCP server on the same machine.
|
|
7
|
-
The model sees Synapsor semantic tools such as
|
|
7
|
+
The model sees Synapsor semantic tools through OpenAI-safe aliases such as
|
|
8
|
+
`billing__inspect_invoice`. Runner keeps the canonical Synapsor capability
|
|
9
|
+
name, such as `billing.inspect_invoice`, in MCP metadata and maps calls back to
|
|
10
|
+
it. The model
|
|
8
11
|
does not receive raw SQL, database URLs, write credentials, approval tools, or
|
|
9
12
|
commit tools.
|
|
10
13
|
|
|
14
|
+
## Smoke Check
|
|
15
|
+
|
|
16
|
+
Run this without an OpenAI API key:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
make smoke
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Expected output:
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
OpenAI Agents stdio example smoke passed.
|
|
26
|
+
```
|
|
27
|
+
|
|
11
28
|
## Prerequisites
|
|
12
29
|
|
|
13
30
|
Generate `synapsor.runner.json` first:
|
|
14
31
|
|
|
15
32
|
```bash
|
|
16
|
-
npx -y -p @synapsor/runner
|
|
33
|
+
npx -y -p @synapsor/runner synapsor-runner demo
|
|
17
34
|
```
|
|
18
35
|
|
|
19
36
|
or connect your own staging database:
|
|
20
37
|
|
|
21
38
|
```bash
|
|
22
|
-
npx -y -p @synapsor/runner
|
|
39
|
+
npx -y -p @synapsor/runner synapsor-runner onboard db --from-env DATABASE_URL
|
|
23
40
|
```
|
|
24
41
|
|
|
25
42
|
Then install the Python dependencies:
|
|
@@ -52,11 +69,12 @@ export SYNAPSOR_INVOICE_ID="INV-3001"
|
|
|
52
69
|
|
|
53
70
|
Expected behavior:
|
|
54
71
|
|
|
55
|
-
- the agent can inspect the scoped invoice through Synapsor
|
|
72
|
+
- the agent can inspect the scoped invoice through Synapsor using an
|
|
73
|
+
OpenAI-safe tool alias;
|
|
74
|
+
- Runner maps the alias back to the canonical Synapsor capability;
|
|
56
75
|
- the agent cannot run SQL;
|
|
57
76
|
- the agent cannot approve or commit writes;
|
|
58
77
|
- evidence/query audit are saved in the local Runner store.
|
|
59
78
|
|
|
60
79
|
If your installed OpenAI Agents SDK does not expose `MCPServerStdio`, update the
|
|
61
|
-
SDK or use the HTTP example
|
|
62
|
-
client.
|
|
80
|
+
SDK or use the Streamable HTTP example.
|
|
@@ -27,14 +27,16 @@ async def main() -> None:
|
|
|
27
27
|
"args": [
|
|
28
28
|
"-y",
|
|
29
29
|
"-p",
|
|
30
|
-
"@synapsor/runner
|
|
31
|
-
"synapsor",
|
|
30
|
+
"@synapsor/runner",
|
|
31
|
+
"synapsor-runner",
|
|
32
32
|
"mcp",
|
|
33
33
|
"serve",
|
|
34
34
|
"--config",
|
|
35
35
|
config_path,
|
|
36
36
|
"--store",
|
|
37
37
|
store_path,
|
|
38
|
+
"--alias-mode",
|
|
39
|
+
"openai",
|
|
38
40
|
],
|
|
39
41
|
"env": {
|
|
40
42
|
**os.environ,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
.PHONY: demo
|
|
2
|
+
|
|
3
|
+
demo:
|
|
4
|
+
@printf '%s\n' "Unsafe shortcut:"
|
|
5
|
+
@printf '%s\n' ' agent -> execute_sql("UPDATE invoices SET late_fee_cents = 0 ...")'
|
|
6
|
+
@printf '%s\n' ""
|
|
7
|
+
@printf '%s\n' "Synapsor quick proof:"
|
|
8
|
+
npx -y @synapsor/runner demo --quick --no-interactive
|
|
9
|
+
@printf '%s\n' ""
|
|
10
|
+
@printf '%s\n' "Risk audit example:"
|
|
11
|
+
npx -y @synapsor/runner audit --example dangerous-db-mcp --format markdown
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Raw SQL vs Synapsor
|
|
2
|
+
|
|
3
|
+
This minimal example shows the fear and the fix without requiring Docker, an
|
|
4
|
+
agent SDK, or a database.
|
|
5
|
+
|
|
6
|
+
## Run
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
make demo
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Expected output includes:
|
|
13
|
+
|
|
14
|
+
```text
|
|
15
|
+
execute_sql
|
|
16
|
+
Synapsor quick demo complete.
|
|
17
|
+
proposal created
|
|
18
|
+
source DB changed: no
|
|
19
|
+
approval required outside MCP
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The raw-SQL shortcut is:
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
agent -> execute_sql("UPDATE invoices SET late_fee_cents = 0 ...")
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The Synapsor path is:
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
agent -> billing.propose_late_fee_waiver(...)
|
|
32
|
+
human approves outside MCP
|
|
33
|
+
guarded writeback applies exactly one row
|
|
34
|
+
replay records what happened
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Why This Exists
|
|
38
|
+
|
|
39
|
+
Use this folder when you want to show the core idea quickly. It is not a full
|
|
40
|
+
database fixture; the full support/billing walkthrough lives in
|
|
41
|
+
`../support-billing-agent/`.
|
|
@@ -36,14 +36,14 @@ examples/reference-support-billing-app/scripts/run-demo.sh
|
|
|
36
36
|
Validate the reviewed contract:
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
|
-
npx -y -p @synapsor/runner
|
|
40
|
-
npx -y -p @synapsor/runner
|
|
39
|
+
npx -y -p @synapsor/runner synapsor-runner config validate --config examples/reference-support-billing-app/synapsor.runner.json
|
|
40
|
+
npx -y -p @synapsor/runner synapsor-runner doctor --config examples/reference-support-billing-app/synapsor.runner.json
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
Serve MCP:
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
|
-
npx -y -p @synapsor/runner
|
|
46
|
+
npx -y -p @synapsor/runner synapsor-runner mcp serve \
|
|
47
47
|
--config examples/reference-support-billing-app/synapsor.runner.json \
|
|
48
48
|
--store ./tmp/reference-support-billing/local.db
|
|
49
49
|
```
|
|
@@ -69,11 +69,11 @@ After `synapsor-runner demo` or after starting this fixture manually, try the sa
|
|
|
69
69
|
proposal-first loop without connecting an MCP client:
|
|
70
70
|
|
|
71
71
|
```bash
|
|
72
|
-
npx -y -p @synapsor/runner
|
|
73
|
-
npx -y -p @synapsor/runner
|
|
74
|
-
npx -y -p @synapsor/runner
|
|
75
|
-
npx -y -p @synapsor/runner
|
|
76
|
-
npx -y -p @synapsor/runner
|
|
72
|
+
npx -y -p @synapsor/runner synapsor-runner propose billing.propose_late_fee_waiver --sample
|
|
73
|
+
npx -y -p @synapsor/runner synapsor-runner proposals show latest
|
|
74
|
+
npx -y -p @synapsor/runner synapsor-runner proposals approve latest --yes
|
|
75
|
+
npx -y -p @synapsor/runner synapsor-runner apply latest
|
|
76
|
+
npx -y -p @synapsor/runner synapsor-runner replay latest
|
|
77
77
|
```
|
|
78
78
|
|
|
79
79
|
Expected safety output:
|
|
@@ -93,8 +93,8 @@ Guarded writeback applied.
|
|
|
93
93
|
Other proposal examples use the same review/apply/replay path:
|
|
94
94
|
|
|
95
95
|
```bash
|
|
96
|
-
npx -y -p @synapsor/runner
|
|
97
|
-
npx -y -p @synapsor/runner
|
|
96
|
+
npx -y -p @synapsor/runner synapsor-runner propose support.propose_plan_credit --sample
|
|
97
|
+
npx -y -p @synapsor/runner synapsor-runner propose orders.propose_status_change --sample
|
|
98
98
|
```
|
|
99
99
|
|
|
100
100
|
Safety guarantees:
|
|
@@ -115,17 +115,17 @@ or production-scale runner orchestration.
|
|
|
115
115
|
After a proposal exists:
|
|
116
116
|
|
|
117
117
|
```bash
|
|
118
|
-
npx -y -p @synapsor/runner
|
|
119
|
-
npx -y -p @synapsor/runner
|
|
120
|
-
npx -y -p @synapsor/runner
|
|
121
|
-
npx -y -p @synapsor/runner
|
|
122
|
-
npx -y -p @synapsor/runner
|
|
118
|
+
npx -y -p @synapsor/runner synapsor-runner proposals list --store ./tmp/reference-support-billing/local.db
|
|
119
|
+
npx -y -p @synapsor/runner synapsor-runner proposals approve <proposal_id> --store ./tmp/reference-support-billing/local.db --actor local_reviewer --yes
|
|
120
|
+
npx -y -p @synapsor/runner synapsor-runner proposals writeback-job <proposal_id> --store ./tmp/reference-support-billing/local.db --output ./tmp/reference-support-billing/job.json
|
|
121
|
+
npx -y -p @synapsor/runner synapsor-runner apply --job ./tmp/reference-support-billing/job.json --store ./tmp/reference-support-billing/local.db
|
|
122
|
+
npx -y -p @synapsor/runner synapsor-runner replay export <proposal_id> --store ./tmp/reference-support-billing/local.db --output ./tmp/reference-support-billing/replay.json
|
|
123
123
|
```
|
|
124
124
|
|
|
125
125
|
To inspect locally in a browser:
|
|
126
126
|
|
|
127
127
|
```bash
|
|
128
|
-
npx -y -p @synapsor/runner
|
|
128
|
+
npx -y -p @synapsor/runner synapsor-runner ui \
|
|
129
129
|
--config examples/reference-support-billing-app/synapsor.runner.json \
|
|
130
130
|
--store ./tmp/reference-support-billing/local.db
|
|
131
131
|
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
.PHONY: demo unsafe clean
|
|
2
|
+
|
|
3
|
+
demo:
|
|
4
|
+
cd ../.. && examples/support-billing-agent/scripts/run-demo.sh
|
|
5
|
+
|
|
6
|
+
unsafe:
|
|
7
|
+
@printf '%s\n' "Unsafe raw-SQL shortcut this demo avoids:"
|
|
8
|
+
@printf '%s\n' ""
|
|
9
|
+
@printf '%s\n' ' agent -> execute_sql("UPDATE invoices SET late_fee_cents = 0 WHERE status = '\''overdue'\''")'
|
|
10
|
+
@printf '%s\n' ""
|
|
11
|
+
@printf '%s\n' "Why it is risky:"
|
|
12
|
+
@printf '%s\n' " - the model controls SQL shape and scope"
|
|
13
|
+
@printf '%s\n' " - tenant filters can be omitted or hallucinated"
|
|
14
|
+
@printf '%s\n' " - there is no proposal, approval boundary, conflict receipt, or replay"
|
|
15
|
+
@printf '%s\n' ""
|
|
16
|
+
@printf '%s\n' "Run 'make demo' to see the Synapsor path: semantic tool -> proposal -> approval -> guarded writeback -> replay."
|
|
17
|
+
|
|
18
|
+
clean:
|
|
19
|
+
docker compose down -v --remove-orphans
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Support/Billing Agent Demo
|
|
2
|
+
|
|
3
|
+
This is the flagship Synapsor Runner demo.
|
|
4
|
+
|
|
5
|
+
It uses a disposable Postgres support/billing app and shows the full loop:
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
agent inspects scoped evidence
|
|
9
|
+
-> agent creates a proposal
|
|
10
|
+
-> source database is unchanged
|
|
11
|
+
-> human approves outside MCP
|
|
12
|
+
-> guarded writeback applies exactly one row
|
|
13
|
+
-> replay shows evidence, diff, approval, receipt, and conflict behavior
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Run It
|
|
17
|
+
|
|
18
|
+
From this folder:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
make demo
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Expected end state:
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
Reference support/billing app smoke passed.
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The model-facing tools are exactly:
|
|
31
|
+
|
|
32
|
+
- `support.inspect_ticket`
|
|
33
|
+
- `support.propose_plan_credit`
|
|
34
|
+
- `billing.inspect_invoice`
|
|
35
|
+
- `billing.propose_late_fee_waiver`
|
|
36
|
+
|
|
37
|
+
The demo proves:
|
|
38
|
+
|
|
39
|
+
- the model-facing tool list contains semantic capabilities such as
|
|
40
|
+
`billing.inspect_invoice` and `billing.propose_late_fee_waiver`;
|
|
41
|
+
- the model does not receive `execute_sql`, approval tools, commit/apply tools,
|
|
42
|
+
database URLs, write credentials, arbitrary table names, arbitrary column
|
|
43
|
+
names, or tenant authority;
|
|
44
|
+
- proposals do not mutate the source database;
|
|
45
|
+
- approved writeback uses tenant, primary-key, allowed-column,
|
|
46
|
+
idempotency, and `updated_at` conflict guards;
|
|
47
|
+
- replay contains the evidence and writeback receipt.
|
|
48
|
+
|
|
49
|
+
## Compare The Unsafe Shortcut
|
|
50
|
+
|
|
51
|
+
To see why the boundary matters:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
make unsafe
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
That target prints the raw-SQL shape this demo avoids. It does not mutate the
|
|
58
|
+
fixture; it shows the path an MCP client should not expose to the model:
|
|
59
|
+
|
|
60
|
+
```text
|
|
61
|
+
execute_sql("UPDATE invoices SET late_fee_cents = 0 ...")
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Use `make demo` for the real proposal, approval, writeback, idempotent retry,
|
|
65
|
+
stale-row conflict, and replay proof.
|
|
66
|
+
|
|
67
|
+
## What This Wraps
|
|
68
|
+
|
|
69
|
+
This folder is self-contained for readers and agents looking for the canonical
|
|
70
|
+
support/billing demo. The smoke logic is shared with the reference fixture so
|
|
71
|
+
the product-facing walkthrough and package tests exercise the same safety
|
|
72
|
+
checks.
|
|
73
|
+
|
|
74
|
+
Relevant files:
|
|
75
|
+
|
|
76
|
+
- `db/schema.sql`
|
|
77
|
+
- `db/seed.sql`
|
|
78
|
+
- `synapsor.runner.json`
|
|
79
|
+
- `scripts/run-demo.sh`
|
|
80
|
+
- `app/README.md`
|
|
81
|
+
|
|
82
|
+
## Stop And Clean Up
|
|
83
|
+
|
|
84
|
+
The demo script cleans up its disposable Docker database automatically. If you
|
|
85
|
+
interrupt it, run:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
make clean
|
|
89
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# App Surface
|
|
2
|
+
|
|
3
|
+
This folder marks the customer application boundary for the flagship demo.
|
|
4
|
+
|
|
5
|
+
The model talks to Synapsor Runner MCP tools. It does not receive application
|
|
6
|
+
write credentials or direct SQL authority. After approval, the trusted runner
|
|
7
|
+
uses the reviewed `synapsor.runner.json` contract to apply the one-row
|
|
8
|
+
writeback with tenant, primary-key, allowed-column, idempotency, and
|
|
9
|
+
`updated_at` conflict guards.
|
|
10
|
+
|
|
11
|
+
For richer app actions, such as inserting a credit ledger row and emitting an
|
|
12
|
+
event, use the app-owned handler examples in `../app-owned-writeback/` and
|
|
13
|
+
`../mcp-postgres-billing-app-handler/`.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS public.tenants (
|
|
2
|
+
id text PRIMARY KEY,
|
|
3
|
+
name text NOT NULL,
|
|
4
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
5
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
CREATE TABLE IF NOT EXISTS public.customers (
|
|
9
|
+
id text PRIMARY KEY,
|
|
10
|
+
tenant_id text NOT NULL REFERENCES public.tenants(id),
|
|
11
|
+
name text NOT NULL,
|
|
12
|
+
email text,
|
|
13
|
+
plan text NOT NULL,
|
|
14
|
+
plan_credit_cents integer NOT NULL DEFAULT 0,
|
|
15
|
+
credit_reason text,
|
|
16
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
17
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
CREATE TABLE IF NOT EXISTS public.support_tickets (
|
|
21
|
+
id text PRIMARY KEY,
|
|
22
|
+
tenant_id text NOT NULL REFERENCES public.tenants(id),
|
|
23
|
+
customer_id text NOT NULL REFERENCES public.customers(id),
|
|
24
|
+
subject text NOT NULL,
|
|
25
|
+
status text NOT NULL,
|
|
26
|
+
resolution_note text,
|
|
27
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
CREATE TABLE IF NOT EXISTS public.invoices (
|
|
31
|
+
id text PRIMARY KEY,
|
|
32
|
+
tenant_id text NOT NULL REFERENCES public.tenants(id),
|
|
33
|
+
customer_id text NOT NULL REFERENCES public.customers(id),
|
|
34
|
+
status text NOT NULL,
|
|
35
|
+
balance_cents integer NOT NULL,
|
|
36
|
+
late_fee_cents integer NOT NULL,
|
|
37
|
+
waiver_reason text,
|
|
38
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
CREATE TABLE IF NOT EXISTS public.credits (
|
|
42
|
+
id text PRIMARY KEY,
|
|
43
|
+
tenant_id text NOT NULL REFERENCES public.tenants(id),
|
|
44
|
+
customer_id text NOT NULL REFERENCES public.customers(id),
|
|
45
|
+
invoice_id text REFERENCES public.invoices(id),
|
|
46
|
+
amount_cents integer NOT NULL,
|
|
47
|
+
reason text NOT NULL,
|
|
48
|
+
status text NOT NULL,
|
|
49
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
50
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
CREATE TABLE IF NOT EXISTS public.agent_actions (
|
|
54
|
+
id text PRIMARY KEY,
|
|
55
|
+
tenant_id text NOT NULL REFERENCES public.tenants(id),
|
|
56
|
+
action_type text NOT NULL,
|
|
57
|
+
target_type text NOT NULL,
|
|
58
|
+
target_id text NOT NULL,
|
|
59
|
+
proposal_id text,
|
|
60
|
+
status text NOT NULL,
|
|
61
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
CREATE TABLE IF NOT EXISTS public.orders (
|
|
65
|
+
id text PRIMARY KEY,
|
|
66
|
+
tenant_id text NOT NULL REFERENCES public.tenants(id),
|
|
67
|
+
customer_id text NOT NULL REFERENCES public.customers(id),
|
|
68
|
+
status text NOT NULL,
|
|
69
|
+
status_change_reason text,
|
|
70
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
DO $$
|
|
74
|
+
BEGIN
|
|
75
|
+
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'synapsor_reader') THEN
|
|
76
|
+
CREATE ROLE synapsor_reader LOGIN PASSWORD 'synapsor_reader_password';
|
|
77
|
+
END IF;
|
|
78
|
+
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'synapsor_writer') THEN
|
|
79
|
+
CREATE ROLE synapsor_writer LOGIN PASSWORD 'synapsor_writer_password';
|
|
80
|
+
END IF;
|
|
81
|
+
END
|
|
82
|
+
$$;
|
|
83
|
+
|
|
84
|
+
GRANT CONNECT ON DATABASE synapsor_support_billing_agent TO synapsor_reader, synapsor_writer;
|
|
85
|
+
GRANT USAGE ON SCHEMA public TO synapsor_reader, synapsor_writer;
|
|
86
|
+
GRANT CREATE ON SCHEMA public TO synapsor_writer;
|
|
87
|
+
GRANT SELECT ON public.tenants, public.customers, public.support_tickets, public.invoices, public.credits, public.agent_actions, public.orders TO synapsor_reader, synapsor_writer;
|
|
88
|
+
GRANT UPDATE (plan_credit_cents, credit_reason, updated_at) ON public.customers TO synapsor_writer;
|
|
89
|
+
GRANT UPDATE (status, resolution_note, updated_at) ON public.support_tickets TO synapsor_writer;
|
|
90
|
+
GRANT UPDATE (late_fee_cents, waiver_reason, updated_at) ON public.invoices TO synapsor_writer;
|
|
91
|
+
GRANT UPDATE (status, status_change_reason, updated_at) ON public.orders TO synapsor_writer;
|