zambo-leadsignal 1.0.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.
- zambo_leadsignal-1.0.0/PKG-INFO +92 -0
- zambo_leadsignal-1.0.0/README.md +71 -0
- zambo_leadsignal-1.0.0/pyproject.toml +39 -0
- zambo_leadsignal-1.0.0/setup.cfg +4 -0
- zambo_leadsignal-1.0.0/zambo_leadsignal/SKILL.md +61 -0
- zambo_leadsignal-1.0.0/zambo_leadsignal/__init__.py +10 -0
- zambo_leadsignal-1.0.0/zambo_leadsignal/cli.py +40 -0
- zambo_leadsignal-1.0.0/zambo_leadsignal/client.py +193 -0
- zambo_leadsignal-1.0.0/zambo_leadsignal/run.py +89 -0
- zambo_leadsignal-1.0.0/zambo_leadsignal.egg-info/PKG-INFO +92 -0
- zambo_leadsignal-1.0.0/zambo_leadsignal.egg-info/SOURCES.txt +13 -0
- zambo_leadsignal-1.0.0/zambo_leadsignal.egg-info/dependency_links.txt +1 -0
- zambo_leadsignal-1.0.0/zambo_leadsignal.egg-info/entry_points.txt +2 -0
- zambo_leadsignal-1.0.0/zambo_leadsignal.egg-info/requires.txt +1 -0
- zambo_leadsignal-1.0.0/zambo_leadsignal.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zambo-leadsignal
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Run a lead signal sweep for a local trade market on Zambo: live signals, screened input, continuity-checked, brief written, every step on a verifiable AER-1 receipt.
|
|
5
|
+
Author-email: Brennan Zambo <brennanzambo@zambo.dev>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://zambo.dev
|
|
8
|
+
Project-URL: Get started, https://zambo.dev/add/
|
|
9
|
+
Project-URL: Guide, https://rambozambodotdev.gitlab.io/aer1-hub/handover-graph.html
|
|
10
|
+
Project-URL: Repository, https://github.com/zambodotdev/zambo-leadsignal
|
|
11
|
+
Keywords: ai-agents,mcp,model-context-protocol,handover,receipts,zambo,aer-1
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: requests>=2.28
|
|
21
|
+
|
|
22
|
+
# zambo-leadsignal
|
|
23
|
+
|
|
24
|
+
Run a lead signal sweep for a local trade market: live signals,
|
|
25
|
+
screened input, your own prior record checked before you write the
|
|
26
|
+
brief, and a verifiable AER-1 receipt for every step.
|
|
27
|
+
|
|
28
|
+
This is the most-run job shape on Zambo, packaged. In a five-day
|
|
29
|
+
first-hand count of the public receipt stream (16,221 receipts,
|
|
30
|
+
October 29 to November 2, 2026), the chain inside this package,
|
|
31
|
+
capability_search then prompt_shield then leadsignal then
|
|
32
|
+
continuity_get_recent then the write-up, appeared in roughly 200
|
|
33
|
+
caller-days per day. Agents were already running it by hand. Now it
|
|
34
|
+
installs.
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
Python:
|
|
39
|
+
|
|
40
|
+
pip install zambo-leadsignal
|
|
41
|
+
zambo-leadsignal run --trade plumber --city "Austin Texas"
|
|
42
|
+
|
|
43
|
+
Node:
|
|
44
|
+
|
|
45
|
+
npm install zambo-leadsignal
|
|
46
|
+
npx zambo-leadsignal run --trade plumber --city "Austin Texas"
|
|
47
|
+
|
|
48
|
+
## What a run does
|
|
49
|
+
|
|
50
|
+
1. Opens the job on the record (memory_remember under the run's job id).
|
|
51
|
+
2. capability_search: the service landscape for the trade and city.
|
|
52
|
+
3. prompt_shield: screens the operator note before it steers the run.
|
|
53
|
+
4. leadsignal: the live lead signals for that trade and city.
|
|
54
|
+
5. continuity_get_recent: what this agent already found, so repeat
|
|
55
|
+
runs build on the record instead of starting from zero.
|
|
56
|
+
6. Writes leadsignal-brief-<job>.md with the signals, the landscape,
|
|
57
|
+
the screen result, the prior record, and every receipt link.
|
|
58
|
+
7. workflow_close: wraps the run. The wrap binds only this run's own
|
|
59
|
+
receipts, so the brief's proof layer is exactly the work that made
|
|
60
|
+
it.
|
|
61
|
+
|
|
62
|
+
Every receipt recomputes free at https://zambo.dev/verify/. A receipt
|
|
63
|
+
proves the saved result was not changed since the call ran; it does
|
|
64
|
+
not prove a lead is good. The brief says so, out loud, because that
|
|
65
|
+
honesty is the product.
|
|
66
|
+
|
|
67
|
+
## Why Zambo is inside
|
|
68
|
+
|
|
69
|
+
The pattern runs on Zambo (https://zambo.dev/mcp), the verifiable
|
|
70
|
+
work layer for AI: 153 tools behind one MCP endpoint, memory and
|
|
71
|
+
continuity across AIs, and a verifiable AER-1 receipt for every job.
|
|
72
|
+
The package teaches the pattern by running it; Zambo is the mechanism
|
|
73
|
+
underneath: identity, memory, the signals tool, and the receipts.
|
|
74
|
+
|
|
75
|
+
Free tier: 20 calls and 1 wrap-up free every day, and picking up in
|
|
76
|
+
another AI is always free. Need more? The $0.99 Day Pass makes it
|
|
77
|
+
unlimited for 24 hours: https://zambo.dev/day-pass/
|
|
78
|
+
|
|
79
|
+
Add Zambo to your own AI in one URL, no key, no account:
|
|
80
|
+
https://zambo.dev/add/
|
|
81
|
+
|
|
82
|
+
## Configuration
|
|
83
|
+
|
|
84
|
+
- ZAMBO_TRADE, ZAMBO_CITY, ZAMBO_NOTE: the run's inputs.
|
|
85
|
+
- ZAMBO_AGENT_ID: this agent's id (default: leadsignal-agent). One
|
|
86
|
+
identity per agent; the caller handle issued at initialize is saved
|
|
87
|
+
under ./.zambo-leadsignal and reused on later runs.
|
|
88
|
+
- ZAMBO_MCP_URL: endpoint override (default: https://zambo.dev/mcp).
|
|
89
|
+
|
|
90
|
+
## License
|
|
91
|
+
|
|
92
|
+
MIT. Built by Brennan Zambo (brennanzambo@zambo.dev).
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# zambo-leadsignal
|
|
2
|
+
|
|
3
|
+
Run a lead signal sweep for a local trade market: live signals,
|
|
4
|
+
screened input, your own prior record checked before you write the
|
|
5
|
+
brief, and a verifiable AER-1 receipt for every step.
|
|
6
|
+
|
|
7
|
+
This is the most-run job shape on Zambo, packaged. In a five-day
|
|
8
|
+
first-hand count of the public receipt stream (16,221 receipts,
|
|
9
|
+
October 29 to November 2, 2026), the chain inside this package,
|
|
10
|
+
capability_search then prompt_shield then leadsignal then
|
|
11
|
+
continuity_get_recent then the write-up, appeared in roughly 200
|
|
12
|
+
caller-days per day. Agents were already running it by hand. Now it
|
|
13
|
+
installs.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
Python:
|
|
18
|
+
|
|
19
|
+
pip install zambo-leadsignal
|
|
20
|
+
zambo-leadsignal run --trade plumber --city "Austin Texas"
|
|
21
|
+
|
|
22
|
+
Node:
|
|
23
|
+
|
|
24
|
+
npm install zambo-leadsignal
|
|
25
|
+
npx zambo-leadsignal run --trade plumber --city "Austin Texas"
|
|
26
|
+
|
|
27
|
+
## What a run does
|
|
28
|
+
|
|
29
|
+
1. Opens the job on the record (memory_remember under the run's job id).
|
|
30
|
+
2. capability_search: the service landscape for the trade and city.
|
|
31
|
+
3. prompt_shield: screens the operator note before it steers the run.
|
|
32
|
+
4. leadsignal: the live lead signals for that trade and city.
|
|
33
|
+
5. continuity_get_recent: what this agent already found, so repeat
|
|
34
|
+
runs build on the record instead of starting from zero.
|
|
35
|
+
6. Writes leadsignal-brief-<job>.md with the signals, the landscape,
|
|
36
|
+
the screen result, the prior record, and every receipt link.
|
|
37
|
+
7. workflow_close: wraps the run. The wrap binds only this run's own
|
|
38
|
+
receipts, so the brief's proof layer is exactly the work that made
|
|
39
|
+
it.
|
|
40
|
+
|
|
41
|
+
Every receipt recomputes free at https://zambo.dev/verify/. A receipt
|
|
42
|
+
proves the saved result was not changed since the call ran; it does
|
|
43
|
+
not prove a lead is good. The brief says so, out loud, because that
|
|
44
|
+
honesty is the product.
|
|
45
|
+
|
|
46
|
+
## Why Zambo is inside
|
|
47
|
+
|
|
48
|
+
The pattern runs on Zambo (https://zambo.dev/mcp), the verifiable
|
|
49
|
+
work layer for AI: 153 tools behind one MCP endpoint, memory and
|
|
50
|
+
continuity across AIs, and a verifiable AER-1 receipt for every job.
|
|
51
|
+
The package teaches the pattern by running it; Zambo is the mechanism
|
|
52
|
+
underneath: identity, memory, the signals tool, and the receipts.
|
|
53
|
+
|
|
54
|
+
Free tier: 20 calls and 1 wrap-up free every day, and picking up in
|
|
55
|
+
another AI is always free. Need more? The $0.99 Day Pass makes it
|
|
56
|
+
unlimited for 24 hours: https://zambo.dev/day-pass/
|
|
57
|
+
|
|
58
|
+
Add Zambo to your own AI in one URL, no key, no account:
|
|
59
|
+
https://zambo.dev/add/
|
|
60
|
+
|
|
61
|
+
## Configuration
|
|
62
|
+
|
|
63
|
+
- ZAMBO_TRADE, ZAMBO_CITY, ZAMBO_NOTE: the run's inputs.
|
|
64
|
+
- ZAMBO_AGENT_ID: this agent's id (default: leadsignal-agent). One
|
|
65
|
+
identity per agent; the caller handle issued at initialize is saved
|
|
66
|
+
under ./.zambo-leadsignal and reused on later runs.
|
|
67
|
+
- ZAMBO_MCP_URL: endpoint override (default: https://zambo.dev/mcp).
|
|
68
|
+
|
|
69
|
+
## License
|
|
70
|
+
|
|
71
|
+
MIT. Built by Brennan Zambo (brennanzambo@zambo.dev).
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "zambo-leadsignal"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Run a lead signal sweep for a local trade market on Zambo: live signals, screened input, continuity-checked, brief written, every step on a verifiable AER-1 receipt."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Brennan Zambo", email = "brennanzambo@zambo.dev" }
|
|
14
|
+
]
|
|
15
|
+
keywords = ["ai-agents", "mcp", "model-context-protocol", "handover", "receipts", "zambo", "aer-1"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 5 - Production/Stable",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Topic :: Software Development :: Libraries",
|
|
22
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence"
|
|
23
|
+
]
|
|
24
|
+
dependencies = ["requests>=2.28"]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://zambo.dev"
|
|
28
|
+
"Get started" = "https://zambo.dev/add/"
|
|
29
|
+
Guide = "https://rambozambodotdev.gitlab.io/aer1-hub/handover-graph.html"
|
|
30
|
+
Repository = "https://github.com/zambodotdev/zambo-leadsignal"
|
|
31
|
+
|
|
32
|
+
[project.scripts]
|
|
33
|
+
zambo-leadsignal = "zambo_leadsignal.cli:main"
|
|
34
|
+
|
|
35
|
+
[tool.setuptools]
|
|
36
|
+
packages = ["zambo_leadsignal"]
|
|
37
|
+
|
|
38
|
+
[tool.setuptools.package-data]
|
|
39
|
+
zambo_leadsignal = ["SKILL.md"]
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# The Lead Signal Run
|
|
2
|
+
|
|
3
|
+
A repeatable sweep that turns a trade and a city into a worked lead
|
|
4
|
+
brief, with every step on a verifiable receipt.
|
|
5
|
+
|
|
6
|
+
This is the single most-run job shape on Zambo: in a five-day count of
|
|
7
|
+
the public receipt stream (16,221 receipts, October 29 to November 2,
|
|
8
|
+
2026), the chain capability_search, prompt_shield, leadsignal,
|
|
9
|
+
continuity_get_recent, write appeared in roughly 200 caller-days per
|
|
10
|
+
day. This package is that chain, named, ordered, and wrapped the way
|
|
11
|
+
the record rewards.
|
|
12
|
+
|
|
13
|
+
## When to run it
|
|
14
|
+
|
|
15
|
+
You serve, or hunt customers for, a local trade: plumber, HVAC,
|
|
16
|
+
electrician, roofer, dentist, lawyer, any service people search for
|
|
17
|
+
when something just broke. Run the sweep when you need this week's
|
|
18
|
+
leads for one market, and run it again next week: the continuity step
|
|
19
|
+
is what makes the second run smarter than the first.
|
|
20
|
+
|
|
21
|
+
## The five steps, in order
|
|
22
|
+
|
|
23
|
+
1. **Open on the record.** Save the run's brief to memory under the
|
|
24
|
+
job id. If it is not written down, it did not happen.
|
|
25
|
+
2. **capability_search** the landscape for the trade and city: what
|
|
26
|
+
services, angles, and offers exist before you spend a signal pull.
|
|
27
|
+
3. **prompt_shield** the operator's targeting note. Notes steer the
|
|
28
|
+
run; screen them like any other input, especially pasted ones.
|
|
29
|
+
4. **leadsignal** for the trade and city. This is the live pull: the
|
|
30
|
+
signals themselves, saved as the step's result.
|
|
31
|
+
5. **continuity_get_recent** before you write a word of the brief.
|
|
32
|
+
What did this agent already find in this market? Chase what is new,
|
|
33
|
+
note what is repeat, never pitch the same lead twice by accident.
|
|
34
|
+
|
|
35
|
+
Then write the brief (signals first, landscape second, receipts last)
|
|
36
|
+
and **workflow_close** the job. The wrap binds only this run's
|
|
37
|
+
receipts. Work done outside the job cannot be wrapped into it later;
|
|
38
|
+
that refusal is the feature.
|
|
39
|
+
|
|
40
|
+
## Rules that keep the run honest
|
|
41
|
+
|
|
42
|
+
- One caller handle per agent, saved at initialize, presented on every
|
|
43
|
+
call. Handles are identity; sharing one merges two agents into one
|
|
44
|
+
record.
|
|
45
|
+
- A receipt proves the saved result was not changed since the call
|
|
46
|
+
ran. It does not prove a lead is good. Say exactly that in the brief.
|
|
47
|
+
- Pickup is free forever: hand the wrapped job to another agent (or
|
|
48
|
+
your own other AI) and they continue from the record, not from a
|
|
49
|
+
summary.
|
|
50
|
+
|
|
51
|
+
## Run it
|
|
52
|
+
|
|
53
|
+
pip install zambo-leadsignal
|
|
54
|
+
zambo-leadsignal run --trade plumber --city "Austin Texas"
|
|
55
|
+
|
|
56
|
+
npm install zambo-leadsignal
|
|
57
|
+
npx zambo-leadsignal run --trade plumber --city "Austin Texas"
|
|
58
|
+
|
|
59
|
+
Free tier: 20 calls and 1 wrap-up free every day per caller, picking
|
|
60
|
+
up in another AI always free. The Day Pass ($0.99) makes it unlimited
|
|
61
|
+
for 24 hours: https://zambo.dev/day-pass/
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""zambo-leadsignal: run a lead signal sweep for a local trade market.
|
|
2
|
+
|
|
3
|
+
The pattern the live Zambo stream runs 200+ times a day, packaged:
|
|
4
|
+
search the capability landscape, shield the input, pull live lead
|
|
5
|
+
signals for a trade and city, check your own recent record so you do
|
|
6
|
+
not chase the same leads twice, write the brief, and wrap the run so
|
|
7
|
+
every step sits on a verifiable AER-1 receipt.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
__version__ = "1.0.0"
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""zambo-leadsignal command line.
|
|
2
|
+
|
|
3
|
+
zambo-leadsignal run --trade plumber --city "Austin Texas"
|
|
4
|
+
zambo-leadsignal run --trade roofer --city Denver --note "storm damage focus"
|
|
5
|
+
|
|
6
|
+
Configuration also by environment: ZAMBO_TRADE, ZAMBO_CITY,
|
|
7
|
+
ZAMBO_NOTE, ZAMBO_AGENT_ID, ZAMBO_MCP_URL.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
from .run import run_sweep
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _arg(flag, env, default=None):
|
|
17
|
+
if flag in sys.argv:
|
|
18
|
+
return sys.argv[sys.argv.index(flag) + 1]
|
|
19
|
+
return os.environ.get(env, default)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def main():
|
|
23
|
+
if len(sys.argv) < 2 or sys.argv[1] != "run":
|
|
24
|
+
print(__doc__)
|
|
25
|
+
return 2
|
|
26
|
+
trade = _arg("--trade", "ZAMBO_TRADE")
|
|
27
|
+
city = _arg("--city", "ZAMBO_CITY")
|
|
28
|
+
note = _arg("--note", "ZAMBO_NOTE")
|
|
29
|
+
if not trade or not city:
|
|
30
|
+
print("run needs --trade and --city (or ZAMBO_TRADE / ZAMBO_CITY)")
|
|
31
|
+
return 2
|
|
32
|
+
result = run_sweep(trade, city, note=note,
|
|
33
|
+
agent_id=os.environ.get("ZAMBO_AGENT_ID",
|
|
34
|
+
"leadsignal-agent"))
|
|
35
|
+
print(f"done: job {result['job_id']}")
|
|
36
|
+
return 0
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
if __name__ == "__main__":
|
|
40
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""Minimal Zambo MCP client: one agent identity, done right.
|
|
2
|
+
|
|
3
|
+
Step zero of the leadsignal pattern lives here. On initialize, the endpoint
|
|
4
|
+
issues a caller handle (in the initialize result's _meta and in the
|
|
5
|
+
X-Zambo-Caller-Handle response header). This client saves that handle
|
|
6
|
+
in the agent's own state file before anything else happens, and
|
|
7
|
+
presents it as _caller_handle on every call, with the agent's name as
|
|
8
|
+
_caller_label. Memory, job timelines, and the daily free allowance all
|
|
9
|
+
key to that handle. One handle per agent, never shared. Two agents on
|
|
10
|
+
one handle are one agent as far as the record is concerned.
|
|
11
|
+
|
|
12
|
+
Transport: requests when available (it handles proxies correctly),
|
|
13
|
+
standard library urllib otherwise. No other dependencies.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import os
|
|
18
|
+
import re
|
|
19
|
+
import urllib.request
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
import requests
|
|
24
|
+
|
|
25
|
+
_HAS_REQUESTS = True
|
|
26
|
+
except ImportError: # pragma: no cover
|
|
27
|
+
_HAS_REQUESTS = False
|
|
28
|
+
|
|
29
|
+
MCP_URL = os.environ.get("ZAMBO_MCP_URL", "https://zambo.dev/mcp")
|
|
30
|
+
RUN_URL_RE = re.compile(r"https://zambo\.dev/run/[0-9a-f-]{36}")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def state_dir():
|
|
34
|
+
"""Where this install keeps per-agent state files and contribution
|
|
35
|
+
notes. Defaults to ./.zambo-leadsignal in the working directory; override
|
|
36
|
+
with ZAMBO_LEADSIGNAL_STATE_DIR. State files hold caller handles: keep
|
|
37
|
+
them private, never commit them."""
|
|
38
|
+
return Path(os.environ.get("ZAMBO_LEADSIGNAL_STATE_DIR", Path.cwd() / ".zambo-leadsignal"))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class ZamboClient:
|
|
42
|
+
"""One agent's session with the Zambo MCP endpoint."""
|
|
43
|
+
|
|
44
|
+
def __init__(self, agent_id, agent_name):
|
|
45
|
+
self.agent_id = agent_id
|
|
46
|
+
self.agent_name = agent_name
|
|
47
|
+
self.session_id = None
|
|
48
|
+
self.quota_handle = None
|
|
49
|
+
self._next_id = 0
|
|
50
|
+
self.state_file = state_dir() / f".zambo-state-{agent_id}.json"
|
|
51
|
+
self.caller_handle = self._load_saved_handle()
|
|
52
|
+
|
|
53
|
+
# -- identity persistence -------------------------------------------
|
|
54
|
+
|
|
55
|
+
def _load_saved_handle(self):
|
|
56
|
+
try:
|
|
57
|
+
data = json.loads(self.state_file.read_text())
|
|
58
|
+
return data.get("caller_handle")
|
|
59
|
+
except Exception:
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
def _save_handle(self):
|
|
63
|
+
if self.caller_handle:
|
|
64
|
+
self.state_file.parent.mkdir(parents=True, exist_ok=True)
|
|
65
|
+
self.state_file.write_text(
|
|
66
|
+
json.dumps({"caller_handle": self.caller_handle}, indent=2)
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# -- transport -------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
def _post(self, payload, expect_response=True):
|
|
72
|
+
headers = {
|
|
73
|
+
"Content-Type": "application/json",
|
|
74
|
+
"Accept": "application/json, text/event-stream",
|
|
75
|
+
"X-Agent-Id": self.agent_id,
|
|
76
|
+
"X-Agent-Name": self.agent_name,
|
|
77
|
+
}
|
|
78
|
+
if self.session_id:
|
|
79
|
+
headers["Mcp-Session-Id"] = self.session_id
|
|
80
|
+
|
|
81
|
+
if _HAS_REQUESTS:
|
|
82
|
+
resp = requests.post(
|
|
83
|
+
MCP_URL, json=payload, headers=headers, timeout=60
|
|
84
|
+
)
|
|
85
|
+
resp.raise_for_status()
|
|
86
|
+
if self.session_id is None:
|
|
87
|
+
self.session_id = resp.headers.get("Mcp-Session-Id")
|
|
88
|
+
header_handle = resp.headers.get("X-Zambo-Caller-Handle")
|
|
89
|
+
raw = resp.text
|
|
90
|
+
else:
|
|
91
|
+
body = json.dumps(payload).encode()
|
|
92
|
+
req = urllib.request.Request(MCP_URL, data=body, headers=headers)
|
|
93
|
+
with urllib.request.urlopen(req, timeout=60) as resp:
|
|
94
|
+
if self.session_id is None:
|
|
95
|
+
self.session_id = resp.headers.get("Mcp-Session-Id")
|
|
96
|
+
header_handle = resp.headers.get("X-Zambo-Caller-Handle")
|
|
97
|
+
raw = resp.read().decode()
|
|
98
|
+
|
|
99
|
+
if header_handle and not self.caller_handle:
|
|
100
|
+
self.caller_handle = header_handle
|
|
101
|
+
if not expect_response:
|
|
102
|
+
return None
|
|
103
|
+
return self._parse_rpc(raw)
|
|
104
|
+
|
|
105
|
+
@staticmethod
|
|
106
|
+
def _parse_rpc(raw):
|
|
107
|
+
message = None
|
|
108
|
+
for line in raw.splitlines():
|
|
109
|
+
line = line.strip()
|
|
110
|
+
if line.startswith("data:"):
|
|
111
|
+
try:
|
|
112
|
+
message = json.loads(line[5:])
|
|
113
|
+
except Exception:
|
|
114
|
+
pass
|
|
115
|
+
if message is None:
|
|
116
|
+
message = json.loads(raw)
|
|
117
|
+
if "error" in message:
|
|
118
|
+
raise RuntimeError(f"MCP error: {message['error']}")
|
|
119
|
+
return message.get("result", {})
|
|
120
|
+
|
|
121
|
+
# -- MCP surface -----------------------------------------------------
|
|
122
|
+
|
|
123
|
+
def initialize(self):
|
|
124
|
+
result = self._post(
|
|
125
|
+
{
|
|
126
|
+
"jsonrpc": "2.0",
|
|
127
|
+
"id": self._id(),
|
|
128
|
+
"method": "initialize",
|
|
129
|
+
"params": {
|
|
130
|
+
"protocolVersion": "2025-03-26",
|
|
131
|
+
"capabilities": {},
|
|
132
|
+
"clientInfo": {"name": self.agent_name, "version": "1.0.0"},
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
)
|
|
136
|
+
meta = result.get("_meta", {})
|
|
137
|
+
if not self.caller_handle:
|
|
138
|
+
self.caller_handle = meta.get("caller_handle")
|
|
139
|
+
self.quota_handle = meta.get("quota_handle")
|
|
140
|
+
self._save_handle()
|
|
141
|
+
self._post(
|
|
142
|
+
{"jsonrpc": "2.0", "method": "notifications/initialized"},
|
|
143
|
+
expect_response=False,
|
|
144
|
+
)
|
|
145
|
+
return result
|
|
146
|
+
|
|
147
|
+
def call_tool(self, name, arguments):
|
|
148
|
+
# The caller handle rides in the arguments, the quota handle in
|
|
149
|
+
# _meta, and the agent's id as the caller label, so the shared
|
|
150
|
+
# job's record attributes every step to this agent.
|
|
151
|
+
args = dict(arguments)
|
|
152
|
+
if self.caller_handle:
|
|
153
|
+
args["_caller_handle"] = self.caller_handle
|
|
154
|
+
args.setdefault("_caller_label", self.agent_id)
|
|
155
|
+
params = {"name": name, "arguments": args}
|
|
156
|
+
if self.quota_handle:
|
|
157
|
+
params["_meta"] = {"quota_handle": self.quota_handle}
|
|
158
|
+
result = self._post(
|
|
159
|
+
{
|
|
160
|
+
"jsonrpc": "2.0",
|
|
161
|
+
"id": self._id(),
|
|
162
|
+
"method": "tools/call",
|
|
163
|
+
"params": params,
|
|
164
|
+
}
|
|
165
|
+
)
|
|
166
|
+
handle = (result.get("structuredContent") or {}).get("caller_handle")
|
|
167
|
+
if handle and not self.caller_handle:
|
|
168
|
+
self.caller_handle = handle
|
|
169
|
+
self._save_handle()
|
|
170
|
+
return result
|
|
171
|
+
|
|
172
|
+
def _id(self):
|
|
173
|
+
self._next_id += 1
|
|
174
|
+
return self._next_id
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def receipt_link(result):
|
|
178
|
+
"""Pull the AER-1 receipt link out of a tool result, if present."""
|
|
179
|
+
structured = result.get("structuredContent") or {}
|
|
180
|
+
for key in ("receipt_url", "run_url", "url"):
|
|
181
|
+
value = structured.get(key)
|
|
182
|
+
if isinstance(value, str) and "/run/" in value:
|
|
183
|
+
return value
|
|
184
|
+
match = RUN_URL_RE.search(json.dumps(result))
|
|
185
|
+
return match.group(0) if match else None
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def result_text(result):
|
|
189
|
+
parts = []
|
|
190
|
+
for block in result.get("content") or []:
|
|
191
|
+
if block.get("type") == "text":
|
|
192
|
+
parts.append(block.get("text", ""))
|
|
193
|
+
return "\n".join(parts)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""The lead signal run: five receipted steps, one brief, one wrap.
|
|
2
|
+
|
|
3
|
+
capability_search what services and angles exist for this trade
|
|
4
|
+
prompt_shield screen the operator note before it steers work
|
|
5
|
+
leadsignal the live signals for (trade, city)
|
|
6
|
+
continuity_get_recent what this agent already found, so a repeat
|
|
7
|
+
run builds on the record instead of
|
|
8
|
+
starting from zero
|
|
9
|
+
workflow_close wrap the run; the wrap binds the job's own
|
|
10
|
+
receipts into one checkable record
|
|
11
|
+
|
|
12
|
+
Every call carries the run's job id as _session_id, so the wrap holds
|
|
13
|
+
exactly this run's steps and nothing else's.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import re
|
|
18
|
+
import time
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
from .client import ZamboClient, receipt_link, result_text
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def slug(text):
|
|
25
|
+
return re.sub(r"[^a-z0-9]+", "-", text.lower()).strip("-")[:40]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def run_sweep(trade, city, note=None, agent_id="leadsignal-agent",
|
|
29
|
+
agent_name="Lead Signal Agent"):
|
|
30
|
+
stamp = time.strftime("%Y%m%d-%H%M%S", time.gmtime())
|
|
31
|
+
job_id = f"leadsignal-{slug(trade)}-{slug(city)}-{stamp}"
|
|
32
|
+
client = ZamboClient(agent_id, agent_name)
|
|
33
|
+
client.initialize()
|
|
34
|
+
receipts = {}
|
|
35
|
+
|
|
36
|
+
def step(tool, args):
|
|
37
|
+
result = client.call_tool(tool, {**args, "_session_id": job_id})
|
|
38
|
+
link = receipt_link(result)
|
|
39
|
+
receipts[tool] = link
|
|
40
|
+
print(f"[{tool}] receipt: {link}")
|
|
41
|
+
return result_text(result)
|
|
42
|
+
|
|
43
|
+
# Open the job on the record.
|
|
44
|
+
brief_fact = (
|
|
45
|
+
f"Lead signal run {job_id}: trade={trade}, city={city}. "
|
|
46
|
+
f"Operator note: {note or '(none)'}. Steps: capability_search, "
|
|
47
|
+
"prompt_shield, leadsignal, continuity_get_recent, wrap."
|
|
48
|
+
)
|
|
49
|
+
step("memory_remember", {"fact": brief_fact[:480],
|
|
50
|
+
"agent_name": agent_name})
|
|
51
|
+
|
|
52
|
+
landscape = step("capability_search",
|
|
53
|
+
{"q": f"{trade} services {city} lead generation"})
|
|
54
|
+
shield_input = note or f"Find new {trade} customers in {city} this week."
|
|
55
|
+
shield = step("prompt_shield", {"prompt": shield_input, "mode": "fast"})
|
|
56
|
+
signals = step("leadsignal", {"trade": trade, "city": city})
|
|
57
|
+
recent = step("continuity_get_recent", {"limit": 20})
|
|
58
|
+
|
|
59
|
+
brief = (
|
|
60
|
+
f"# Lead signal brief: {trade} in {city}\n\n"
|
|
61
|
+
f"Job: {job_id} (UTC {stamp})\n\n"
|
|
62
|
+
f"## Live signals (leadsignal)\n\n{signals}\n\n"
|
|
63
|
+
f"## Capability landscape (capability_search)\n\n{landscape}\n\n"
|
|
64
|
+
f"## Input screen (prompt_shield)\n\n{shield}\n\n"
|
|
65
|
+
f"## What this agent already had on record (continuity)\n\n"
|
|
66
|
+
f"{recent}\n\n"
|
|
67
|
+
f"## Receipts\n\n"
|
|
68
|
+
+ "\n".join(f"- {t}: {u}" for t, u in receipts.items() if u)
|
|
69
|
+
+ "\n\nEvery receipt recomputes at https://zambo.dev/verify/. "
|
|
70
|
+
"A receipt proves the saved result was not changed since the "
|
|
71
|
+
"call ran; it does not prove a lead is good. Work the signals, "
|
|
72
|
+
"and let the record keep you honest about what you already "
|
|
73
|
+
"chased.\n"
|
|
74
|
+
)
|
|
75
|
+
out = Path.cwd() / f"leadsignal-brief-{job_id}.md"
|
|
76
|
+
out.write_text(brief)
|
|
77
|
+
print(f"[brief] written: {out}")
|
|
78
|
+
|
|
79
|
+
wrap = client.call_tool(
|
|
80
|
+
"workflow_close",
|
|
81
|
+
{"_session_id": job_id,
|
|
82
|
+
"summary": f"Lead signal run for {trade} in {city}: live signals "
|
|
83
|
+
f"pulled, input screened, prior record checked, brief "
|
|
84
|
+
f"written to {out.name}."},
|
|
85
|
+
)
|
|
86
|
+
wrap_link = receipt_link(wrap)
|
|
87
|
+
print(f"[wrap] workflow_close receipt: {wrap_link}")
|
|
88
|
+
return {"job_id": job_id, "brief": str(out), "receipts": receipts,
|
|
89
|
+
"wrap_receipt": wrap_link}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zambo-leadsignal
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Run a lead signal sweep for a local trade market on Zambo: live signals, screened input, continuity-checked, brief written, every step on a verifiable AER-1 receipt.
|
|
5
|
+
Author-email: Brennan Zambo <brennanzambo@zambo.dev>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://zambo.dev
|
|
8
|
+
Project-URL: Get started, https://zambo.dev/add/
|
|
9
|
+
Project-URL: Guide, https://rambozambodotdev.gitlab.io/aer1-hub/handover-graph.html
|
|
10
|
+
Project-URL: Repository, https://github.com/zambodotdev/zambo-leadsignal
|
|
11
|
+
Keywords: ai-agents,mcp,model-context-protocol,handover,receipts,zambo,aer-1
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: requests>=2.28
|
|
21
|
+
|
|
22
|
+
# zambo-leadsignal
|
|
23
|
+
|
|
24
|
+
Run a lead signal sweep for a local trade market: live signals,
|
|
25
|
+
screened input, your own prior record checked before you write the
|
|
26
|
+
brief, and a verifiable AER-1 receipt for every step.
|
|
27
|
+
|
|
28
|
+
This is the most-run job shape on Zambo, packaged. In a five-day
|
|
29
|
+
first-hand count of the public receipt stream (16,221 receipts,
|
|
30
|
+
October 29 to November 2, 2026), the chain inside this package,
|
|
31
|
+
capability_search then prompt_shield then leadsignal then
|
|
32
|
+
continuity_get_recent then the write-up, appeared in roughly 200
|
|
33
|
+
caller-days per day. Agents were already running it by hand. Now it
|
|
34
|
+
installs.
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
Python:
|
|
39
|
+
|
|
40
|
+
pip install zambo-leadsignal
|
|
41
|
+
zambo-leadsignal run --trade plumber --city "Austin Texas"
|
|
42
|
+
|
|
43
|
+
Node:
|
|
44
|
+
|
|
45
|
+
npm install zambo-leadsignal
|
|
46
|
+
npx zambo-leadsignal run --trade plumber --city "Austin Texas"
|
|
47
|
+
|
|
48
|
+
## What a run does
|
|
49
|
+
|
|
50
|
+
1. Opens the job on the record (memory_remember under the run's job id).
|
|
51
|
+
2. capability_search: the service landscape for the trade and city.
|
|
52
|
+
3. prompt_shield: screens the operator note before it steers the run.
|
|
53
|
+
4. leadsignal: the live lead signals for that trade and city.
|
|
54
|
+
5. continuity_get_recent: what this agent already found, so repeat
|
|
55
|
+
runs build on the record instead of starting from zero.
|
|
56
|
+
6. Writes leadsignal-brief-<job>.md with the signals, the landscape,
|
|
57
|
+
the screen result, the prior record, and every receipt link.
|
|
58
|
+
7. workflow_close: wraps the run. The wrap binds only this run's own
|
|
59
|
+
receipts, so the brief's proof layer is exactly the work that made
|
|
60
|
+
it.
|
|
61
|
+
|
|
62
|
+
Every receipt recomputes free at https://zambo.dev/verify/. A receipt
|
|
63
|
+
proves the saved result was not changed since the call ran; it does
|
|
64
|
+
not prove a lead is good. The brief says so, out loud, because that
|
|
65
|
+
honesty is the product.
|
|
66
|
+
|
|
67
|
+
## Why Zambo is inside
|
|
68
|
+
|
|
69
|
+
The pattern runs on Zambo (https://zambo.dev/mcp), the verifiable
|
|
70
|
+
work layer for AI: 153 tools behind one MCP endpoint, memory and
|
|
71
|
+
continuity across AIs, and a verifiable AER-1 receipt for every job.
|
|
72
|
+
The package teaches the pattern by running it; Zambo is the mechanism
|
|
73
|
+
underneath: identity, memory, the signals tool, and the receipts.
|
|
74
|
+
|
|
75
|
+
Free tier: 20 calls and 1 wrap-up free every day, and picking up in
|
|
76
|
+
another AI is always free. Need more? The $0.99 Day Pass makes it
|
|
77
|
+
unlimited for 24 hours: https://zambo.dev/day-pass/
|
|
78
|
+
|
|
79
|
+
Add Zambo to your own AI in one URL, no key, no account:
|
|
80
|
+
https://zambo.dev/add/
|
|
81
|
+
|
|
82
|
+
## Configuration
|
|
83
|
+
|
|
84
|
+
- ZAMBO_TRADE, ZAMBO_CITY, ZAMBO_NOTE: the run's inputs.
|
|
85
|
+
- ZAMBO_AGENT_ID: this agent's id (default: leadsignal-agent). One
|
|
86
|
+
identity per agent; the caller handle issued at initialize is saved
|
|
87
|
+
under ./.zambo-leadsignal and reused on later runs.
|
|
88
|
+
- ZAMBO_MCP_URL: endpoint override (default: https://zambo.dev/mcp).
|
|
89
|
+
|
|
90
|
+
## License
|
|
91
|
+
|
|
92
|
+
MIT. Built by Brennan Zambo (brennanzambo@zambo.dev).
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
zambo_leadsignal/SKILL.md
|
|
4
|
+
zambo_leadsignal/__init__.py
|
|
5
|
+
zambo_leadsignal/cli.py
|
|
6
|
+
zambo_leadsignal/client.py
|
|
7
|
+
zambo_leadsignal/run.py
|
|
8
|
+
zambo_leadsignal.egg-info/PKG-INFO
|
|
9
|
+
zambo_leadsignal.egg-info/SOURCES.txt
|
|
10
|
+
zambo_leadsignal.egg-info/dependency_links.txt
|
|
11
|
+
zambo_leadsignal.egg-info/entry_points.txt
|
|
12
|
+
zambo_leadsignal.egg-info/requires.txt
|
|
13
|
+
zambo_leadsignal.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
requests>=2.28
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
zambo_leadsignal
|