zambo-loop 1.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- zambo_loop/__init__.py +3 -0
- zambo_loop/__main__.py +4 -0
- zambo_loop/bundle/RUN.md +101 -0
- zambo_loop/bundle/SKILL.md +182 -0
- zambo_loop/bundle/loop.py +437 -0
- zambo_loop/bundle/manifest.json +1 -0
- zambo_loop/bundle/pastes/daily-loop.txt +7 -0
- zambo_loop/cli.py +55 -0
- zambo_loop-1.0.1.dist-info/METADATA +209 -0
- zambo_loop-1.0.1.dist-info/RECORD +14 -0
- zambo_loop-1.0.1.dist-info/WHEEL +5 -0
- zambo_loop-1.0.1.dist-info/entry_points.txt +2 -0
- zambo_loop-1.0.1.dist-info/licenses/LICENSE +21 -0
- zambo_loop-1.0.1.dist-info/top_level.txt +1 -0
zambo_loop/__init__.py
ADDED
zambo_loop/__main__.py
ADDED
zambo_loop/bundle/RUN.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Daily loop run: the proof record
|
|
2
|
+
|
|
3
|
+
Executed live against https://zambo.dev/mcp on November 2, 2026.
|
|
4
|
+
Job id (_session_id): `daily-loop-demo-2026-11-02`. One identity,
|
|
5
|
+
`daily-loop-agent`, issued a fresh caller handle at initialize and
|
|
6
|
+
persisted in a state file; three sessions ran under that same handle
|
|
7
|
+
as separate processes. Pattern: recall, work, remember, wrap. This
|
|
8
|
+
record includes the two refusals the run hit on the way, because they
|
|
9
|
+
are the two lessons the package exists to teach.
|
|
10
|
+
|
|
11
|
+
## Session 1: set the watch (20:46 UTC)
|
|
12
|
+
|
|
13
|
+
- `live_price` BTC: $76,786.70, down 1.29% (24h), 20:46:29 UTC.
|
|
14
|
+
Receipt: https://zambo.dev/run/3ab3edf9-68c1-4419-b444-49853ddc7fca
|
|
15
|
+
- `live_price` ETH: $2,247.14, down 0.69% (24h), 20:46:32 UTC.
|
|
16
|
+
Receipt: https://zambo.dev/run/326f0be5-f1fe-432c-a67d-01e56105531b
|
|
17
|
+
- `memory_remember` the baseline state: REFUSED before execution.
|
|
18
|
+
The fact ran over the 500 character limit for a saved fact; the
|
|
19
|
+
endpoint rejected it with `invalid_tool_arguments` and issued the
|
|
20
|
+
refusal its own receipt:
|
|
21
|
+
https://zambo.dev/run/c7d11d3b-64da-4625-b246-42243674bf7c
|
|
22
|
+
Nothing was saved. A receipt link is not proof a call succeeded;
|
|
23
|
+
the result said so plainly, and the next session proved it.
|
|
24
|
+
|
|
25
|
+
## Session 2: the honest empty recall (20:46 UTC)
|
|
26
|
+
|
|
27
|
+
- `memory_recall` for the watch state returned status `empty`,
|
|
28
|
+
count 0, because session 1's save had never landed.
|
|
29
|
+
Receipt: https://zambo.dev/run/4adc6ece-9458-4e19-af64-6e4587b1dbbc
|
|
30
|
+
- `live_price` BTC again: $76,848.93, 20:46:52 UTC.
|
|
31
|
+
Receipt: https://zambo.dev/run/dc2f54b4-aaa0-460d-b823-f951724bb102
|
|
32
|
+
- `memory_remember` the session 2 update (inside the limit): saved.
|
|
33
|
+
Receipt: https://zambo.dev/run/43a95682-e2dc-4833-8dbe-cc43bd605670
|
|
34
|
+
- `handover` wrap attempt: REFUSED with `receipt_access_denied`.
|
|
35
|
+
The wrap's receipt selection named the refused remember's receipt,
|
|
36
|
+
which is not an executed step of this job; a wrap holds only
|
|
37
|
+
receipts from its own job, and nothing was published.
|
|
38
|
+
Receipt: https://zambo.dev/run/5dad54f0-073d-4f1a-aecd-0d736d0d60c8
|
|
39
|
+
|
|
40
|
+
## Repair: save the baseline, short (20:49 UTC)
|
|
41
|
+
|
|
42
|
+
The same baseline state, written at 378 characters, saved cleanly:
|
|
43
|
+
BTC baseline $76,786.70 and ETH baseline $2,247.14 with their price
|
|
44
|
+
receipts inside the fact, and the alert rule in plain words (flag if
|
|
45
|
+
BTC moves 3 percent or more from baseline, or ETH 4 percent or more).
|
|
46
|
+
Receipt: https://zambo.dev/run/ba4a4143-d6db-4a4f-9834-af5056abd08f
|
|
47
|
+
|
|
48
|
+
## Session 3: the loop closes (20:49 to 20:50 UTC)
|
|
49
|
+
|
|
50
|
+
A third session, a new MCP session under the same saved handle:
|
|
51
|
+
|
|
52
|
+
- `memory_recall` returned the baseline fact, and the check was
|
|
53
|
+
mechanical: the strings "BTC baseline $76,786.70", "ETH baseline
|
|
54
|
+
$2,247.14", and the alert rule all appear in the recalled text
|
|
55
|
+
verbatim, exactly as saved.
|
|
56
|
+
Receipt: https://zambo.dev/run/fd2d6820-cc7d-4474-995f-2b04ef707140
|
|
57
|
+
- `live_price` BTC: $76,875.00, 20:49:54 UTC, a move of +0.115%
|
|
58
|
+
from the $76,786.70 baseline. Inside the 3 percent band: no alert.
|
|
59
|
+
Receipt: https://zambo.dev/run/e42b60ed-1dee-4360-bc4c-9b07ccb08250
|
|
60
|
+
- `memory_remember` the session 3 update: saved.
|
|
61
|
+
Receipt: https://zambo.dev/run/1630f1f9-991e-40c1-a358-431c82dacae6
|
|
62
|
+
- `handover` wrap, its receipt selection naming only this job's
|
|
63
|
+
executed steps (the two session 1 prices, the saved baseline
|
|
64
|
+
remember, the session 3 price, the session 3 remember):
|
|
65
|
+
https://zambo.dev/handover/60db250a-bf2a-4c8c-86da-b2ec8b7e150c
|
|
66
|
+
JSON twin: https://zambo.dev/handover/60db250a-bf2a-4c8c-86da-b2ec8b7e150c.json
|
|
67
|
+
Wrap receipt: https://zambo.dev/run/307d76af-cd16-4c2a-bd06-3206f6821c3c
|
|
68
|
+
The handover lists exactly those 5 steps, in order.
|
|
69
|
+
|
|
70
|
+
## Accounting
|
|
71
|
+
|
|
72
|
+
The loop's shape, per session: work and memory calls only. Session 1
|
|
73
|
+
spent 2 counted calls (the prices; the refused remember executed
|
|
74
|
+
nothing). The repair remember spent 1. Session 3 spent 3 (recall,
|
|
75
|
+
price, remember) and its wrap was the free daily wrap-up. The whole
|
|
76
|
+
proof, refusals and diagnostics included, stayed inside one identity's
|
|
77
|
+
single day of free tier: 20 calls + 1 wrap-up free every day, keyed to
|
|
78
|
+
the caller handle, and picking up a handover is always free.
|
|
79
|
+
|
|
80
|
+
## The two lessons, which are the package
|
|
81
|
+
|
|
82
|
+
1. A saved fact has a hard limit of 500 characters. Write facts like
|
|
83
|
+
headlines with receipts attached, not essays. And check the result
|
|
84
|
+
of every call: a refused call still hands you a receipt link, and
|
|
85
|
+
the next session's empty recall is what an unchecked refusal costs.
|
|
86
|
+
2. A wrap is a job's own record. It binds the job's executed steps and
|
|
87
|
+
refuses a selection that names anything else. Keep the job's
|
|
88
|
+
receipt ids as you go, and the wrap is a formality; improvise the
|
|
89
|
+
selection, and the wrap refuses, receipted, with the reason.
|
|
90
|
+
|
|
91
|
+
## Verification
|
|
92
|
+
|
|
93
|
+
Every link above was fetched on 2026-11-02 after the run: all returned
|
|
94
|
+
HTTP 200 (14 of 14, including both refusal receipts, the final
|
|
95
|
+
handover page, and its JSON twin). One identity note, recorded for
|
|
96
|
+
honesty: the first identity minted for this run was issued under this
|
|
97
|
+
machine's shared default User-Agent and inherited a shared, already
|
|
98
|
+
spent quota bucket (0 of 20 free calls before its first call), so it
|
|
99
|
+
was replaced before any work was saved; the run above is the second
|
|
100
|
+
identity, minted with the agent's own User-Agent from its first
|
|
101
|
+
initialize, which is the discipline `loop.py` ships with.
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: daily-loop
|
|
3
|
+
description: Run a daily loop on Zambo: give one agent a persistent caller handle so it recalls yesterday's saved state, does today's work, remembers the update, and wraps the day into a handover the next session picks up. Use when an agent must come back day after day to the same job (a watch, a standup, a digest, a monitor), when work must survive the session that did it, or when a one-shot agent needs to become a standing one. Zambo is the memory and work layer under the loop (endpoint https://zambo.dev/mcp); it does not schedule or wake agents.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# The daily loop on Zambo
|
|
7
|
+
|
|
8
|
+
Most agents are one-shot: they arrive knowing nothing, do the job,
|
|
9
|
+
and evaporate. The daily loop is the opposite shape. One agent, one
|
|
10
|
+
persistent identity, and a four-beat rhythm it repeats every session:
|
|
11
|
+
recall, work, remember, wrap. Yesterday's session leaves state in
|
|
12
|
+
memory and a handover as the day's deliverable; today's session picks
|
|
13
|
+
the handover up, recalls the state verbatim, does the work, and
|
|
14
|
+
writes the next state down. Memory is not a feature of this pattern.
|
|
15
|
+
Memory is the pattern: it is what gives the agent a reason, and the
|
|
16
|
+
means, to come back.
|
|
17
|
+
|
|
18
|
+
This skill ships inside the zambo-daily-loop package. Next to it you
|
|
19
|
+
will find `loop.py`, a runnable single-agent loop; `pastes/` with the
|
|
20
|
+
no-code standing instructions for chat agents; and `RUN.md`, a real
|
|
21
|
+
three-session run on the live endpoint with every receipt linked,
|
|
22
|
+
including the two refusals the run hit and what they teach.
|
|
23
|
+
|
|
24
|
+
## Step zero: your handle is your memory
|
|
25
|
+
|
|
26
|
+
On initialize, the endpoint issues a caller handle (in the initialize
|
|
27
|
+
result and the `X-Zambo-Caller-Handle` header). Save it in your own
|
|
28
|
+
state file before doing anything else, and present it as
|
|
29
|
+
`_caller_handle` on every call, with your agent name as
|
|
30
|
+
`_caller_label`. Your saved facts, your job timelines, and your daily
|
|
31
|
+
free allowance all key to that handle. Present the same handle in a
|
|
32
|
+
new session and you are the same agent, with yesterday intact. Lose
|
|
33
|
+
it, and you come back as a stranger: no memory, a fresh allowance, no
|
|
34
|
+
continuity. One handle per agent, never shared, never shown in
|
|
35
|
+
ordinary replies. Use a User-Agent of your own from the very first
|
|
36
|
+
initialize: the quota handle is issued against your network identity,
|
|
37
|
+
and a shared default can inherit a shared, already spent bucket.
|
|
38
|
+
|
|
39
|
+
## The four beats
|
|
40
|
+
|
|
41
|
+
1. **Pick up.** If yesterday wrapped a handover, accept it with
|
|
42
|
+
`handover_accept`. Pickup is always free, and the handover returns
|
|
43
|
+
the day's steps and receipts whole.
|
|
44
|
+
2. **Recall.** `memory_recall` the state the job depends on: the
|
|
45
|
+
baselines, the rule, where things stopped. Check the recalled text
|
|
46
|
+
against what the job needs, verbatim, before you trust it. An
|
|
47
|
+
empty recall is information, not an error: it means yesterday
|
|
48
|
+
saved nothing, which usually means a save was refused and nobody
|
|
49
|
+
checked.
|
|
50
|
+
3. **Work.** Do the actual job. Every call returns a verifiable
|
|
51
|
+
receipt; keep the receipt links of the steps that matter.
|
|
52
|
+
4. **Remember, then wrap.** `memory_remember` the one or two facts
|
|
53
|
+
tomorrow needs, then close the day with `handover`: a summary of
|
|
54
|
+
what happened, the goal, a recipient, next steps, and the job's
|
|
55
|
+
receipt ids attached. That handover is the deliverable the next
|
|
56
|
+
session, or the next AI, picks up.
|
|
57
|
+
|
|
58
|
+
## The two disciplines that make or break the loop
|
|
59
|
+
|
|
60
|
+
**Facts are short.** A saved fact has a hard limit of 500 characters.
|
|
61
|
+
A longer `memory_remember` is refused before execution, receipted,
|
|
62
|
+
and nothing is saved. Write facts like headlines with receipts
|
|
63
|
+
attached: the value, the receipt link, the rule, the next action.
|
|
64
|
+
And check the result of every call, not just its receipt link: a
|
|
65
|
+
refused call still hands you a receipt, and the cost of an unchecked
|
|
66
|
+
refusal is tomorrow's empty recall.
|
|
67
|
+
|
|
68
|
+
**A wrap holds only its own job's receipts.** The wrap binds the
|
|
69
|
+
job's executed steps and runs a proof loop over them. Name a receipt
|
|
70
|
+
from outside the job, or one from a call that was refused, and the
|
|
71
|
+
wrap itself is refused, receipted, with the reason. Track the job's
|
|
72
|
+
receipt ids as you work (the prices and the memory saves), and the
|
|
73
|
+
wrap is a formality.
|
|
74
|
+
|
|
75
|
+
## The accounting (why this shape, in numbers)
|
|
76
|
+
|
|
77
|
+
Free tier, per agent: 20 calls + 1 wrap-up free every day, keyed to
|
|
78
|
+
the agent's own caller handle; picking up a handover is always free.
|
|
79
|
+
The run in `RUN.md` ran the loop's sessions at 2 to 3 counted calls
|
|
80
|
+
each, wrap included free, so a daily agent on this shape runs free
|
|
81
|
+
indefinitely. When the agent outgrows the free shape, a Day Pass is
|
|
82
|
+
$0.99, unlimited for 24 hours, and the Zambo Pass is $29 for 30 days;
|
|
83
|
+
both are one-time payments, never subscriptions, on the same
|
|
84
|
+
endpoint. Entry point for a human setting this up:
|
|
85
|
+
https://zambo.dev/add/
|
|
86
|
+
|
|
87
|
+
## What this does not do
|
|
88
|
+
|
|
89
|
+
Zambo does not wake your agent up. Your scheduler, cron, or operator
|
|
90
|
+
starts each session; the loop is what the session does once started.
|
|
91
|
+
It also does not make recalled state correct: a receipt proves the
|
|
92
|
+
saved result was not changed since it was saved, never that the
|
|
93
|
+
answer was right. The loop makes an agent's days add up instead of
|
|
94
|
+
resetting, and makes each day checkable.
|
|
95
|
+
|
|
96
|
+
## The Zambo pattern family: five packages, one work layer
|
|
97
|
+
|
|
98
|
+
Zambo is the verifiable work layer for AI: 153 tools behind one MCP endpoint (https://zambo.dev/mcp), a verifiable AER-1 receipt for every job, and handovers that move work between agents. Free tier: 20 calls + 1 wrap-up free every day per caller handle, picking up a handover is always free. Day Pass: $0.99 for unlimited calls for 24 hours. Zambo Pass: $29 for 30 days. A receipt proves the saved result was not changed; it does not prove the answer was right.
|
|
99
|
+
|
|
100
|
+
The five packages below are one family, proven in one set of live runs on November 2, 2026. The swarm run put 4 agents on one shared job in 8 counted calls and closed it as a 6-step handover: https://zambo.dev/handover/a154789b-0588-43c7-a31c-d108eb59406b . Each package teaches one pattern by doing it, with Zambo as the mechanism underneath. Install one, or compose them: a watch feeds a loop, a loop hands its job to a swarm, delivery proves the result to a client, and verify-pass checks any retelling of that result against the receipt of record.
|
|
101
|
+
|
|
102
|
+
### 1. zambo-swarm: swarm coordination
|
|
103
|
+
|
|
104
|
+
Use it when: the job is bigger than one agent and several workers must each prove their piece of one shared job.
|
|
105
|
+
|
|
106
|
+
Real run: https://zambo.dev/handover/a154789b-0588-43c7-a31c-d108eb59406b (4 agents, 8 counted calls, 6-step handover)
|
|
107
|
+
|
|
108
|
+
- PyPI: https://pypi.org/project/zambo-swarm/
|
|
109
|
+
- npm: https://www.npmjs.com/package/zambo-swarm
|
|
110
|
+
- Repo: https://github.com/zambodotdev/zambo-swarm-coordination
|
|
111
|
+
- Guide: https://rambozambodotdev.gitlab.io/aer1-hub/swarm-coordination-on-zambo.html
|
|
112
|
+
- Article: https://dev.to/rambozambo/four-agents-one-job-zero-trust-a-swarm-where-every-worker-proves-its-piece-35ol
|
|
113
|
+
|
|
114
|
+
### 2. zambo-loop: the daily loop
|
|
115
|
+
|
|
116
|
+
Use it when: an agent must pick up its own work tomorrow, recall its baseline verbatim, and keep one job alive across sessions.
|
|
117
|
+
|
|
118
|
+
Real run: https://zambo.dev/handover/60db250a-bf2a-4c8c-86da-b2ec8b7e150c
|
|
119
|
+
|
|
120
|
+
- npm: https://www.npmjs.com/package/zambo-loop
|
|
121
|
+
- Repo: https://github.com/zambodotdev/zambo-daily-loop
|
|
122
|
+
- Guide: https://rambozambodotdev.gitlab.io/aer1-hub/daily-loop-package.html
|
|
123
|
+
- Article: https://dev.to/rambozambo/the-daily-loop-as-an-installable-package-2e89
|
|
124
|
+
- Note: the PyPI listing for zambo-loop is pending; use npm or the repo for now.
|
|
125
|
+
|
|
126
|
+
### 3. zambo-verify-pass: the verify pass
|
|
127
|
+
|
|
128
|
+
Use it when: someone retells a result and you need to catch the drift against the receipt of record.
|
|
129
|
+
|
|
130
|
+
Real run: https://zambo.dev/handover/fa7444ae-e3c7-4206-ae12-3adb5e749fb0
|
|
131
|
+
|
|
132
|
+
- PyPI: https://pypi.org/project/zambo-verify-pass/
|
|
133
|
+
- npm: https://www.npmjs.com/package/zambo-verify-pass
|
|
134
|
+
- Repo: https://github.com/zambodotdev/zambo-verify-pass
|
|
135
|
+
- Guide: https://rambozambodotdev.gitlab.io/aer1-hub/verify-pass-package.html
|
|
136
|
+
- Article: https://dev.to/rambozambo/the-price-was-wrong-the-receipt-was-not-catching-a-drifted-retelling-with-a-verify-pass-188i
|
|
137
|
+
|
|
138
|
+
### 4. zambo-delivery: delivery proof
|
|
139
|
+
|
|
140
|
+
Use it when: a client must check delivered work step by step and their pickup is the sign-off.
|
|
141
|
+
|
|
142
|
+
Proof handover: https://zambo.dev/handover/31715914-80eb-438d-b01b-d2307cc4800e
|
|
143
|
+
|
|
144
|
+
- PyPI: https://pypi.org/project/zambo-delivery/
|
|
145
|
+
- npm: https://www.npmjs.com/package/zambo-delivery
|
|
146
|
+
- Repo: https://github.com/zambodotdev/zambo-delivery-proof
|
|
147
|
+
- Guide: https://rambozambodotdev.gitlab.io/aer1-hub/delivery-proof-package.html
|
|
148
|
+
- Article: https://dev.to/rambozambo/one-job-one-link-zero-arguments-a-delivery-your-client-checks-step-by-step-4j4
|
|
149
|
+
|
|
150
|
+
### 5. zambo-watch: signal watch
|
|
151
|
+
|
|
152
|
+
Use it when: one watcher checks the same signal on a schedule and must prove what it saw, quiet days included.
|
|
153
|
+
|
|
154
|
+
Digest handover: https://zambo.dev/handover/121aa53f-1981-4330-9133-49e386aabf1e
|
|
155
|
+
|
|
156
|
+
- PyPI: https://pypi.org/project/zambo-watch/
|
|
157
|
+
- npm: https://www.npmjs.com/package/zambo-watch
|
|
158
|
+
- Repo: https://github.com/zambodotdev/zambo-signal-watch
|
|
159
|
+
- Guide: https://rambozambodotdev.gitlab.io/aer1-hub/signal-watch-package.html
|
|
160
|
+
- Article: https://dev.to/rambozambo/one-watcher-three-prices-one-rule-an-agent-watch-that-remembers-its-baseline-34kg
|
|
161
|
+
|
|
162
|
+
### How the five compose
|
|
163
|
+
|
|
164
|
+
1. zambo-watch checks a signal on a schedule and saves what it saw, quiet days included.
|
|
165
|
+
2. A change the watch catches becomes the next run of zambo-loop, which recalls its baseline verbatim and keeps the job alive across sessions.
|
|
166
|
+
3. When the job outgrows one agent, the loop hands it to zambo-swarm, where each worker proves its own piece of the shared job.
|
|
167
|
+
4. zambo-delivery wraps the finished work as one handover the client checks step by step; the client pickup is the sign-off.
|
|
168
|
+
5. zambo-verify-pass checks any later retelling of the result against the receipt of record and catches the drift.
|
|
169
|
+
|
|
170
|
+
Start free: https://zambo.dev/add/ . Verify any receipt: https://zambo.dev/verify/ . The family page: https://rambozambodotdev.gitlab.io/aer1-hub/the-pattern-family.html
|
|
171
|
+
|
|
172
|
+
## Go deeper
|
|
173
|
+
|
|
174
|
+
- Full guide, with the real run and every receipt:
|
|
175
|
+
https://rambozambodotdev.gitlab.io/aer1-hub/daily-loop-package.html
|
|
176
|
+
- Article, the write-up of the live run: https://dev.to/rambozambo/the-daily-loop-as-an-installable-package-2e89
|
|
177
|
+
- This package: https://github.com/zambodotdev/zambo-daily-loop
|
|
178
|
+
- The loop's data story (4,247 callers counted, 17 stayed):
|
|
179
|
+
https://dev.to/rambozambo/4247-callers-came-17-stayed-here-is-the-loop-the-17-run-2o51
|
|
180
|
+
- The starter repo, the loop pre-wired:
|
|
181
|
+
https://github.com/zambodotdev/zambo-memory-loop-starter
|
|
182
|
+
- Setup for the human who runs you: https://zambo.dev/add/
|
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""The daily loop, as runnable code: recall, work, remember, wrap.
|
|
3
|
+
|
|
4
|
+
One agent, one persistent caller handle, one job that survives the
|
|
5
|
+
session. Two commands, because a day has two halves:
|
|
6
|
+
|
|
7
|
+
python3 loop.py start # day one: price the watch, save the
|
|
8
|
+
# baseline state to memory
|
|
9
|
+
python3 loop.py continue # every later session: recall the state
|
|
10
|
+
# verbatim, re-check, remember the
|
|
11
|
+
# update, wrap the day into a handover
|
|
12
|
+
|
|
13
|
+
The handle is the whole trick. On initialize the endpoint issues a
|
|
14
|
+
caller handle; this script saves it in a state file next to itself
|
|
15
|
+
before doing anything else, and presents it on every call, in every
|
|
16
|
+
later process, forever. Memory, the job timeline, and the daily free
|
|
17
|
+
allowance all key to that handle. Lose the state file and the agent
|
|
18
|
+
comes back as a stranger: no memory, no continuity.
|
|
19
|
+
|
|
20
|
+
Three field lessons are baked in, each learned from a real refusal:
|
|
21
|
+
|
|
22
|
+
1. Facts have a hard limit of 500 characters. A longer memory_remember
|
|
23
|
+
is refused before execution, receipted, and nothing is saved. Keep
|
|
24
|
+
facts short; put the receipts in them, not the essay.
|
|
25
|
+
2. A refused call still returns a receipt. This script checks every
|
|
26
|
+
result for errors and stops loudly instead of collecting receipts
|
|
27
|
+
for work that never happened.
|
|
28
|
+
3. A wrap holds only receipts from its own job. The wrap below names
|
|
29
|
+
only this job's executed steps (prices and memory saves), which the
|
|
30
|
+
state file tracks across sessions.
|
|
31
|
+
|
|
32
|
+
Cost: start is 3 counted calls; continue is 3 counted calls (the
|
|
33
|
+
recall, the price, the remember) plus the free daily wrap-up. The free
|
|
34
|
+
tier is 20 calls + 1 wrap-up per day, keyed to the handle, so the loop
|
|
35
|
+
runs free. No API key, no signup. Every call returns an AER-1 receipt
|
|
36
|
+
link; a receipt proves the saved result was not changed, never that
|
|
37
|
+
the answer was right.
|
|
38
|
+
|
|
39
|
+
The pattern, taught end to end with a real run and its receipts:
|
|
40
|
+
https://rambozambodotdev.gitlab.io/aer1-hub/daily-loop-package.html
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
import json
|
|
44
|
+
import os
|
|
45
|
+
import re
|
|
46
|
+
import shutil
|
|
47
|
+
import subprocess
|
|
48
|
+
import sys
|
|
49
|
+
import time
|
|
50
|
+
import urllib.request
|
|
51
|
+
from pathlib import Path
|
|
52
|
+
|
|
53
|
+
MCP_URL = os.environ.get("ZAMBO_MCP_URL", "https://zambo.dev/mcp")
|
|
54
|
+
AGENT_ID = os.environ.get("ZAMBO_AGENT_ID", "daily-loop-agent")
|
|
55
|
+
AGENT_NAME = os.environ.get("ZAMBO_AGENT_NAME", "Daily Loop Agent")
|
|
56
|
+
JOB_ID = os.environ.get("ZAMBO_SESSION_ID", "daily-loop-demo")
|
|
57
|
+
ASSETS = [
|
|
58
|
+
a.strip().upper()
|
|
59
|
+
for a in os.environ.get("ZAMBO_ASSETS", "BTC,ETH").split(",")
|
|
60
|
+
if a.strip()
|
|
61
|
+
]
|
|
62
|
+
ALERT_PCT = {
|
|
63
|
+
"BTC": float(os.environ.get("ZAMBO_ALERT_BTC", "3")),
|
|
64
|
+
"ETH": float(os.environ.get("ZAMBO_ALERT_ETH", "4")),
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
HERE = Path(__file__).parent
|
|
68
|
+
# Step zero: the state file IS the identity. Handle, baselines, and
|
|
69
|
+
# the job's receipt ids live here, across processes and days.
|
|
70
|
+
STATE_FILE = HERE / f".zambo-loop-state-{AGENT_ID}.json"
|
|
71
|
+
|
|
72
|
+
RUN_URL_RE = re.compile(r"https://zambo\.dev/run/[0-9a-f-]{36}")
|
|
73
|
+
PRICE_RE = re.compile(r"\$([0-9][0-9,]*\.?[0-9]*)")
|
|
74
|
+
|
|
75
|
+
FACT_LIMIT = 500
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def fmt_price(p):
|
|
79
|
+
return f"${p:,.2f}"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class ZamboClient:
|
|
83
|
+
"""Minimal MCP client over streamable HTTP, standard library only."""
|
|
84
|
+
|
|
85
|
+
def __init__(self, state):
|
|
86
|
+
self.state = state
|
|
87
|
+
self.session_id = None
|
|
88
|
+
self._header_handle = None
|
|
89
|
+
self._next_id = 0
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def caller_handle(self):
|
|
93
|
+
return self.state.get("caller_handle")
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def quota_handle(self):
|
|
97
|
+
return self.state.get("quota_handle")
|
|
98
|
+
|
|
99
|
+
def _headers(self):
|
|
100
|
+
headers = {
|
|
101
|
+
"Content-Type": "application/json",
|
|
102
|
+
"Accept": "application/json, text/event-stream",
|
|
103
|
+
"X-Agent-Id": AGENT_ID,
|
|
104
|
+
"X-Agent-Name": AGENT_NAME,
|
|
105
|
+
# A User-Agent of the agent's own from the very first
|
|
106
|
+
# initialize: the quota handle is issued against the
|
|
107
|
+
# caller's network identity, and a shared default UA can
|
|
108
|
+
# inherit a shared, already-spent bucket.
|
|
109
|
+
"User-Agent": f"{AGENT_ID}/1.0",
|
|
110
|
+
}
|
|
111
|
+
if self.session_id:
|
|
112
|
+
headers["Mcp-Session-Id"] = self.session_id
|
|
113
|
+
return headers
|
|
114
|
+
|
|
115
|
+
def _absorb_headers(self, header_block):
|
|
116
|
+
for line in header_block.splitlines():
|
|
117
|
+
if ":" not in line:
|
|
118
|
+
continue
|
|
119
|
+
name, value = line.split(":", 1)
|
|
120
|
+
name, value = name.strip().lower(), value.strip()
|
|
121
|
+
if name == "mcp-session-id" and value and self.session_id is None:
|
|
122
|
+
self.session_id = value
|
|
123
|
+
elif name == "x-zambo-caller-handle" and value:
|
|
124
|
+
self._header_handle = value
|
|
125
|
+
|
|
126
|
+
def _post_curl(self, payload):
|
|
127
|
+
# curl is the transport when present: no Python dependencies, and
|
|
128
|
+
# it rides out flaky proxies that clip urllib response bodies.
|
|
129
|
+
cmd = ["curl", "-sS", "--http1.1", "-i", "-X", "POST", MCP_URL,
|
|
130
|
+
"--data-binary", "@-"]
|
|
131
|
+
for k, v in self._headers().items():
|
|
132
|
+
cmd += ["-H", f"{k}: {v}"]
|
|
133
|
+
cmd += ["-H", "Expect:"]
|
|
134
|
+
out = subprocess.run(cmd, input=json.dumps(payload).encode(),
|
|
135
|
+
capture_output=True, timeout=120)
|
|
136
|
+
if out.returncode != 0:
|
|
137
|
+
raise RuntimeError(f"curl transport failed: {out.stderr.decode()[:200]}")
|
|
138
|
+
raw = out.stdout.decode()
|
|
139
|
+
# With -i the header block(s) precede the body; take the last one.
|
|
140
|
+
parts = raw.split("\r\n\r\n")
|
|
141
|
+
if len(parts) < 2:
|
|
142
|
+
parts = raw.split("\n\n")
|
|
143
|
+
body = parts[-1]
|
|
144
|
+
for block in parts[:-1]:
|
|
145
|
+
if "mcp-session-id" in block.lower() or "x-zambo" in block.lower():
|
|
146
|
+
self._absorb_headers(block)
|
|
147
|
+
return body
|
|
148
|
+
|
|
149
|
+
def _post_urllib(self, payload):
|
|
150
|
+
req = urllib.request.Request(
|
|
151
|
+
MCP_URL, data=json.dumps(payload).encode(),
|
|
152
|
+
headers=self._headers(), method="POST")
|
|
153
|
+
with urllib.request.urlopen(req, timeout=90) as resp:
|
|
154
|
+
block = "\n".join(f"{k}: {v}" for k, v in resp.headers.items())
|
|
155
|
+
self._absorb_headers(block)
|
|
156
|
+
return resp.read().decode()
|
|
157
|
+
|
|
158
|
+
def _post(self, payload, expect_response=True):
|
|
159
|
+
raw = None
|
|
160
|
+
last = None
|
|
161
|
+
for attempt in range(3):
|
|
162
|
+
try:
|
|
163
|
+
if shutil.which("curl"):
|
|
164
|
+
raw = self._post_curl(payload)
|
|
165
|
+
else:
|
|
166
|
+
raw = self._post_urllib(payload)
|
|
167
|
+
break
|
|
168
|
+
except Exception as e:
|
|
169
|
+
last = e
|
|
170
|
+
raw = None
|
|
171
|
+
if attempt < 2:
|
|
172
|
+
time.sleep(2 * (attempt + 1))
|
|
173
|
+
if raw is None:
|
|
174
|
+
raise last
|
|
175
|
+
if self._header_handle and not self.caller_handle:
|
|
176
|
+
self.state["caller_handle"] = self._header_handle
|
|
177
|
+
self._save()
|
|
178
|
+
if not expect_response:
|
|
179
|
+
return None
|
|
180
|
+
return self._parse_rpc(raw)
|
|
181
|
+
|
|
182
|
+
@staticmethod
|
|
183
|
+
def _parse_rpc(raw):
|
|
184
|
+
message = None
|
|
185
|
+
for line in raw.splitlines():
|
|
186
|
+
line = line.strip()
|
|
187
|
+
if line.startswith("data:"):
|
|
188
|
+
try:
|
|
189
|
+
message = json.loads(line[5:])
|
|
190
|
+
except Exception:
|
|
191
|
+
pass
|
|
192
|
+
if message is None:
|
|
193
|
+
message = json.loads(raw)
|
|
194
|
+
if "error" in message:
|
|
195
|
+
raise RuntimeError(f"MCP error: {message['error']}")
|
|
196
|
+
return message.get("result", {})
|
|
197
|
+
|
|
198
|
+
def initialize(self):
|
|
199
|
+
params = {
|
|
200
|
+
"protocolVersion": "2025-03-26",
|
|
201
|
+
"capabilities": {},
|
|
202
|
+
"clientInfo": {"name": AGENT_NAME, "version": "1.0.0"},
|
|
203
|
+
}
|
|
204
|
+
if self.caller_handle:
|
|
205
|
+
params["_meta"] = {
|
|
206
|
+
"caller_handle": self.caller_handle,
|
|
207
|
+
"quota_handle": self.quota_handle,
|
|
208
|
+
}
|
|
209
|
+
result = self._post(
|
|
210
|
+
{"jsonrpc": "2.0", "id": self._id(), "method": "initialize", "params": params}
|
|
211
|
+
)
|
|
212
|
+
for container in (result.get("_meta", {}), result.get("meta", {})):
|
|
213
|
+
if not self.caller_handle and container.get("caller_handle"):
|
|
214
|
+
self.state["caller_handle"] = container["caller_handle"]
|
|
215
|
+
if not self.quota_handle and container.get("quota_handle"):
|
|
216
|
+
self.state["quota_handle"] = container["quota_handle"]
|
|
217
|
+
self._save()
|
|
218
|
+
self._post(
|
|
219
|
+
{"jsonrpc": "2.0", "method": "notifications/initialized"},
|
|
220
|
+
expect_response=False,
|
|
221
|
+
)
|
|
222
|
+
return result
|
|
223
|
+
|
|
224
|
+
def call_tool(self, name, arguments):
|
|
225
|
+
args = dict(arguments)
|
|
226
|
+
if self.caller_handle:
|
|
227
|
+
args["_caller_handle"] = self.caller_handle
|
|
228
|
+
args.setdefault("_caller_label", AGENT_ID)
|
|
229
|
+
params = {"name": name, "arguments": args}
|
|
230
|
+
if self.quota_handle:
|
|
231
|
+
params["_meta"] = {
|
|
232
|
+
"quota_handle": self.quota_handle,
|
|
233
|
+
"caller_handle": self.caller_handle,
|
|
234
|
+
}
|
|
235
|
+
result = self._post(
|
|
236
|
+
{"jsonrpc": "2.0", "id": self._id(), "method": "tools/call", "params": params}
|
|
237
|
+
)
|
|
238
|
+
# A refused call still returns a receipt. Check the result
|
|
239
|
+
# itself, never just the receipt link.
|
|
240
|
+
structured = result.get("structuredContent") or {}
|
|
241
|
+
error = structured.get("error") if isinstance(structured, dict) else None
|
|
242
|
+
if result.get("isError") or error:
|
|
243
|
+
raise RuntimeError(f"{name} refused: {json.dumps(error)[:240] if error else result}")
|
|
244
|
+
handle = structured.get("caller_handle")
|
|
245
|
+
if handle and not self.caller_handle:
|
|
246
|
+
self.state["caller_handle"] = handle
|
|
247
|
+
self._save()
|
|
248
|
+
return result
|
|
249
|
+
|
|
250
|
+
def _save(self):
|
|
251
|
+
STATE_FILE.write_text(json.dumps(self.state, indent=2))
|
|
252
|
+
|
|
253
|
+
def _id(self):
|
|
254
|
+
self._next_id += 1
|
|
255
|
+
return self._next_id
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def receipt_link(result):
|
|
259
|
+
structured = result.get("structuredContent") or {}
|
|
260
|
+
for key in ("receipt_url", "run_url", "url"):
|
|
261
|
+
value = structured.get(key)
|
|
262
|
+
if isinstance(value, str) and "/run/" in value:
|
|
263
|
+
return value
|
|
264
|
+
match = RUN_URL_RE.search(json.dumps(result))
|
|
265
|
+
return match.group(0) if match else None
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def result_text(result):
|
|
269
|
+
out = []
|
|
270
|
+
for block in result.get("content", []):
|
|
271
|
+
if block.get("type") == "text":
|
|
272
|
+
out.append(block["text"])
|
|
273
|
+
if result.get("structuredContent"):
|
|
274
|
+
out.append(json.dumps(result["structuredContent"]))
|
|
275
|
+
return "\n".join(out)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def parse_price(result):
|
|
279
|
+
match = PRICE_RE.search(result_text(result))
|
|
280
|
+
return float(match.group(1).replace(",", "")) if match else None
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def load_state():
|
|
284
|
+
try:
|
|
285
|
+
return json.loads(STATE_FILE.read_text())
|
|
286
|
+
except Exception:
|
|
287
|
+
return {}
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def remember(client, state, fact):
|
|
291
|
+
assert len(fact) <= FACT_LIMIT, f"fact is {len(fact)} chars; the limit is {FACT_LIMIT}"
|
|
292
|
+
result = client.call_tool(
|
|
293
|
+
"memory_remember",
|
|
294
|
+
{"fact": fact, "agent_name": AGENT_NAME, "_session_id": JOB_ID},
|
|
295
|
+
)
|
|
296
|
+
link = receipt_link(result)
|
|
297
|
+
if link:
|
|
298
|
+
state.setdefault("receipt_ids", []).append(link.rsplit("/", 1)[-1])
|
|
299
|
+
return link
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def start(client, state):
|
|
303
|
+
# Work: price the watch. Real calls, real receipts.
|
|
304
|
+
baselines = {}
|
|
305
|
+
for symbol in ASSETS:
|
|
306
|
+
result = client.call_tool("live_price", {"symbol": symbol, "_session_id": JOB_ID})
|
|
307
|
+
price = parse_price(result)
|
|
308
|
+
link = receipt_link(result)
|
|
309
|
+
baselines[symbol] = {"price": price, "receipt": link}
|
|
310
|
+
if link:
|
|
311
|
+
state.setdefault("receipt_ids", []).append(link.rsplit("/", 1)[-1])
|
|
312
|
+
print(f"[start] {symbol} {fmt_price(price)} receipt: {link}")
|
|
313
|
+
state["baselines"] = baselines
|
|
314
|
+
state["day"] = time.strftime("%Y-%m-%d", time.gmtime())
|
|
315
|
+
|
|
316
|
+
# Remember: save the state tomorrow's session will recall. Short
|
|
317
|
+
# fact, receipts inside, the rule in plain words.
|
|
318
|
+
parts = [f"Daily loop watch, day 1 baseline, {state['day']}."]
|
|
319
|
+
for symbol in ASSETS:
|
|
320
|
+
parts.append(
|
|
321
|
+
f"{symbol} baseline {fmt_price(baselines[symbol]['price'])} "
|
|
322
|
+
f"(receipt {baselines[symbol]['receipt']})."
|
|
323
|
+
)
|
|
324
|
+
rules = " or ".join(
|
|
325
|
+
f"{s} {ALERT_PCT.get(s, 3):g} percent" for s in ASSETS
|
|
326
|
+
)
|
|
327
|
+
parts.append(
|
|
328
|
+
f"Alert rule: flag if an asset moves from baseline by {rules} or more. "
|
|
329
|
+
"Next session: recall, re-check, compare, remember, wrap."
|
|
330
|
+
)
|
|
331
|
+
link = remember(client, state, " ".join(parts))
|
|
332
|
+
print(f"[start] baseline state remembered receipt: {link}")
|
|
333
|
+
client._save()
|
|
334
|
+
print("day one saved. tomorrow, and every day after: python3 loop.py continue")
|
|
335
|
+
return 0
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def continue_loop(client, state):
|
|
339
|
+
baselines = state.get("baselines")
|
|
340
|
+
if not baselines:
|
|
341
|
+
print("no baseline state in the state file: run `python3 loop.py start` first")
|
|
342
|
+
return 1
|
|
343
|
+
|
|
344
|
+
# Recall: pull yesterday's state back and check it lands verbatim.
|
|
345
|
+
result = client.call_tool(
|
|
346
|
+
"memory_recall",
|
|
347
|
+
{"query": "daily loop watch day 1 baseline alert rule", "_session_id": JOB_ID},
|
|
348
|
+
)
|
|
349
|
+
recalled = result_text(result)
|
|
350
|
+
for symbol, base in baselines.items():
|
|
351
|
+
needle = f"{symbol} baseline {fmt_price(base['price'])}"
|
|
352
|
+
print(f"[recall] {needle!r} verbatim: {needle in recalled}")
|
|
353
|
+
print(f"[recall] receipt: {receipt_link(result)}")
|
|
354
|
+
|
|
355
|
+
# Work: re-check the first asset against its baseline.
|
|
356
|
+
symbol = ASSETS[0]
|
|
357
|
+
base = baselines[symbol]["price"]
|
|
358
|
+
result = client.call_tool("live_price", {"symbol": symbol, "_session_id": JOB_ID})
|
|
359
|
+
price = parse_price(result)
|
|
360
|
+
link = receipt_link(result)
|
|
361
|
+
if link:
|
|
362
|
+
state.setdefault("receipt_ids", []).append(link.rsplit("/", 1)[-1])
|
|
363
|
+
move = (price - base) / base * 100.0
|
|
364
|
+
alert = abs(move) >= ALERT_PCT.get(symbol, 3)
|
|
365
|
+
print(
|
|
366
|
+
f"[work] {symbol} {fmt_price(price)}, {move:+.2f}% from baseline "
|
|
367
|
+
f"{fmt_price(base)}, alert: {alert} receipt: {link}"
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
# Remember: today's update, short enough to save.
|
|
371
|
+
update = (
|
|
372
|
+
f"Daily loop watch, check {time.strftime('%Y-%m-%d', time.gmtime())}. "
|
|
373
|
+
f"{symbol} now {fmt_price(price)}, a move of {move:+.2f} percent from the "
|
|
374
|
+
f"day 1 baseline of {fmt_price(base)} (receipt {link}). "
|
|
375
|
+
f"Alert: {'YES' if alert else 'no'}. Baselines unchanged; tomorrow the loop repeats."
|
|
376
|
+
)
|
|
377
|
+
link = remember(client, state, update)
|
|
378
|
+
print(f"[remember] update saved receipt: {link}")
|
|
379
|
+
|
|
380
|
+
# Wrap: the day becomes a handover, naming only this job's own
|
|
381
|
+
# executed receipts, which the state file tracked across sessions.
|
|
382
|
+
summary = (
|
|
383
|
+
f"Daily loop, job {JOB_ID}. Baselines: "
|
|
384
|
+
+ ", ".join(
|
|
385
|
+
f"{s} {fmt_price(b['price'])} (receipt https://zambo.dev/run/"
|
|
386
|
+
f"{(b['receipt'] or '').rsplit('/', 1)[-1]})"
|
|
387
|
+
for s, b in baselines.items()
|
|
388
|
+
)
|
|
389
|
+
+ f". Latest check: {symbol} {fmt_price(price)}, {move:+.2f}% from baseline, "
|
|
390
|
+
f"alert: {'yes' if alert else 'no'}. The state was recalled verbatim before "
|
|
391
|
+
"the check, and the update was remembered after it."
|
|
392
|
+
)
|
|
393
|
+
result = client.call_tool(
|
|
394
|
+
"handover",
|
|
395
|
+
{
|
|
396
|
+
"summary": summary,
|
|
397
|
+
"goal": "A daily loop whose state survives the session: recalled "
|
|
398
|
+
"verbatim, worked on, updated, and wrapped, every step receipted.",
|
|
399
|
+
"recipient": "Daily Loop Operator",
|
|
400
|
+
"recipient_type": "human",
|
|
401
|
+
"next_steps": [
|
|
402
|
+
"Tomorrow: pick this handover up (pickup is always free), recall "
|
|
403
|
+
"the watch state, re-check the prices, remember the update, wrap "
|
|
404
|
+
"the day again.",
|
|
405
|
+
"Check any receipt at https://zambo.dev/verify/ : a receipt proves "
|
|
406
|
+
"the saved result was not changed since it was saved.",
|
|
407
|
+
],
|
|
408
|
+
"receipt_ids": list(dict.fromkeys(state.get("receipt_ids", []))),
|
|
409
|
+
"_session_id": JOB_ID,
|
|
410
|
+
},
|
|
411
|
+
)
|
|
412
|
+
structured = result.get("structuredContent") or {}
|
|
413
|
+
handover_id = structured.get("handover_id")
|
|
414
|
+
if not handover_id:
|
|
415
|
+
match = re.search(r"handover/([0-9a-f-]{36})", json.dumps(result))
|
|
416
|
+
handover_id = match.group(1) if match else None
|
|
417
|
+
print(f"[wrap] handover: https://zambo.dev/handover/{handover_id}")
|
|
418
|
+
print(f"[wrap] wrap receipt: {receipt_link(result)}")
|
|
419
|
+
client._save()
|
|
420
|
+
print("loop closed: recall, work, remember, wrap.")
|
|
421
|
+
return 0
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def main():
|
|
425
|
+
phase = sys.argv[1] if len(sys.argv) > 1 else ""
|
|
426
|
+
if phase not in ("start", "continue"):
|
|
427
|
+
print("usage: python3 loop.py start|continue")
|
|
428
|
+
return 2
|
|
429
|
+
state = load_state()
|
|
430
|
+
client = ZamboClient(state)
|
|
431
|
+
client.initialize()
|
|
432
|
+
print(f"session open; caller handle bound for agent '{AGENT_ID}'")
|
|
433
|
+
return start(client, state) if phase == "start" else continue_loop(client, state)
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
if __name__ == "__main__":
|
|
437
|
+
sys.exit(main())
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"audience":"agents","contents":[{"path":"SKILL.md","role":"The pattern, taught end to end for an agent reader: step-zero handle discipline, the four beats (pick up, recall, work, remember and wrap), the 500 character fact limit, wrap integrity, accounting."},{"path":"README.md","role":"Agent-facing entry point: what this package makes you able to do and the 5-minute path from reading to a running daily loop."},{"path":"loop.py","role":"Runnable loop: start (price the watch, save the baseline state) and continue (recall verbatim, re-check, remember the update, wrap the day), with handle persistence and refusal checks baked in."},{"path":"pastes/daily-loop.txt","role":"No-code standing instructions for a chat agent running the loop."},{"path":"RUN.md","role":"Proof: the record of a real three-session run on the live endpoint (2026-11-02), with every receipt and handover link, including the two refusals the run hit and repaired."},{"path":"manifest.json","role":"This file."}],"description":"Agent package: learn the daily loop from the skill inside, then run it on Zambo in one step. One agent, one persistent caller handle, state recalled verbatim across sessions, each day wrapped into a handover the next session picks up free.","endpoint":"https://zambo.dev/mcp","free_tier":"Per agent, keyed to its own caller handle: 20 calls + 1 wrap-up free every day; picking up a handover is always free. Day Pass $0.99 per agent, unlimited 24 hours. Zambo Pass $29 per agent, 30 days. One-time payments, never subscriptions.","links":{"add_zambo":"https://zambo.dev/add/","article":"https://dev.to/rambozambo/the-daily-loop-as-an-installable-package-2e89","data_story":"https://dev.to/rambozambo/4247-callers-came-17-stayed-here-is-the-loop-the-17-run-2o51","guide":"https://rambozambodotdev.gitlab.io/aer1-hub/daily-loop-package.html","guide_markdown":"https://rambozambodotdev.gitlab.io/aer1-hub/daily-loop-package.md","package_repo":"https://github.com/zambodotdev/zambo-daily-loop","sibling_swarm_github":"https://github.com/zambodotdev/zambo-swarm-coordination","sibling_swarm_pypi":"https://pypi.org/project/zambo-swarm/","starter_repo":"https://github.com/zambodotdev/zambo-memory-loop-starter","verify":"https://zambo.dev/verify/"},"name":"zambo-daily-loop","positioning":"Zambo is the memory and work layer under the loop (persistent per-agent memory, handovers between sessions, verifiable receipts). It is not a scheduler: it does not wake or run agents.","proof":{"final_handover":"https://zambo.dev/handover/60db250a-bf2a-4c8c-86da-b2ec8b7e150c","job_id":"daily-loop-demo-2026-11-02","run_date":"2026-11-02","wrap_receipt":"https://zambo.dev/run/307d76af-cd16-4c2a-bd06-3206f6821c3c"},"released":"2026-11-02","version":"1.0.1"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Run a daily loop with me on Zambo:
|
|
2
|
+
1. Connect to https://zambo.dev/mcp. When the endpoint issues your caller handle, save it in durable state you control and present it on every call, in every session. You are the same agent every day because that handle says so; your memory and your daily free allowance (20 calls + 1 wrap-up) key to it. Never share it.
|
|
3
|
+
2. Start each session by picking up: if a handover from the last session exists, accept it with handover_accept (picking up is always free) and read its steps.
|
|
4
|
+
3. Recall the job's state with memory_recall and check the lines the job depends on (baselines, rules, where things stopped) appear verbatim before you rely on them. An empty recall means the last session saved nothing; say so plainly instead of inventing state.
|
|
5
|
+
4. Do the day's work. Keep the receipt link of every call that matters.
|
|
6
|
+
5. Remember the update with memory_remember: one short fact, never over 500 characters, holding the new values, their receipt links, and the next action. Then check the result: a refused save still returns a receipt, and a save that did not land means tomorrow starts empty.
|
|
7
|
+
6. Wrap the day with handover: a summary of what happened, the goal, the operator named as recipient, next steps, and only this job's own receipt ids attached. That handover is the deliverable: the record the next session, or a different AI, picks up to continue. A receipt proves the saved result was not changed since it was saved, never that the answer was right.
|
zambo_loop/cli.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""zambo-loop: the daily loop on Zambo, in one installable package.
|
|
2
|
+
|
|
3
|
+
Prints the 5-minute path from reading to a running daily loop and
|
|
4
|
+
where the package contents (the skill, the runnable loop, the no-code
|
|
5
|
+
paste, and the proof run) are installed. No network, no API key; the
|
|
6
|
+
pattern itself runs on the live endpoint at https://zambo.dev/mcp.
|
|
7
|
+
"""
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
BUNDLE = Path(__file__).resolve().parent / "bundle"
|
|
11
|
+
|
|
12
|
+
PATH_TEXT = """\
|
|
13
|
+
zambo-loop: the daily loop on Zambo, in one package.
|
|
14
|
+
|
|
15
|
+
The 5-minute path, from reading to a running loop:
|
|
16
|
+
|
|
17
|
+
Route A, no code (you are a chat agent):
|
|
18
|
+
1. Connect to https://zambo.dev/mcp. On initialize the endpoint
|
|
19
|
+
issues your caller handle. Save it durably and present it on
|
|
20
|
+
every call, in every session. Everything keys to it.
|
|
21
|
+
2. Follow pastes/daily-loop.txt in the bundle: pick up, recall,
|
|
22
|
+
work, remember (one short fact, never over 500 characters),
|
|
23
|
+
wrap the day.
|
|
24
|
+
3. The next session picks the handover up for free and recalls
|
|
25
|
+
the state verbatim. Nothing restarts from zero.
|
|
26
|
+
|
|
27
|
+
Route B, code (you run Python):
|
|
28
|
+
1. Read SKILL.md once. It is the full pattern in one file.
|
|
29
|
+
2. Day one: python3 loop.py start
|
|
30
|
+
Prices the watch, saves the baseline state to memory, writes
|
|
31
|
+
its identity to a state file next to the script.
|
|
32
|
+
3. Every later session: python3 loop.py continue
|
|
33
|
+
Recalls the state verbatim, re-checks, remembers the update,
|
|
34
|
+
wraps the day into a handover.
|
|
35
|
+
|
|
36
|
+
Free per agent, keyed to its own caller handle: 20 calls + 1
|
|
37
|
+
wrap-up free every day; picking up a handover is always free.
|
|
38
|
+
Endpoint: https://zambo.dev/mcp
|
|
39
|
+
Guide: https://rambozambodotdev.gitlab.io/aer1-hub/daily-loop-package.html
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def main():
|
|
44
|
+
print(PATH_TEXT)
|
|
45
|
+
print("Package contents are installed at:")
|
|
46
|
+
print(f" {BUNDLE}")
|
|
47
|
+
if BUNDLE.is_dir():
|
|
48
|
+
for path in sorted(BUNDLE.rglob("*")):
|
|
49
|
+
if path.is_file():
|
|
50
|
+
print(f" - {path.relative_to(BUNDLE)}")
|
|
51
|
+
return 0
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
if __name__ == "__main__":
|
|
55
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zambo-loop
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: The daily loop for AI agents on Zambo: one persistent caller handle, recall yesterday's state verbatim, work, remember, wrap the day into a handover. Free per agent: 20 calls + 1 wrap-up every day; pickup always free.
|
|
5
|
+
Author: Brennan Zambo
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://zambo.dev
|
|
8
|
+
Project-URL: Add Zambo to your AI, https://zambo.dev/add/
|
|
9
|
+
Project-URL: Daily loop guide, https://rambozambodotdev.gitlab.io/aer1-hub/daily-loop-package.html
|
|
10
|
+
Project-URL: Package source, https://github.com/zambodotdev/zambo-daily-loop
|
|
11
|
+
Project-URL: Verify a receipt, https://zambo.dev/verify/
|
|
12
|
+
Keywords: mcp,ai,agents,memory,continuity,daily-loop,handover,receipts,automation,llm
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Requires-Python: >=3.8
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# zambo-daily-loop: an agent package
|
|
25
|
+
|
|
26
|
+
You are an agent, and this package is addressed to you. Reading it
|
|
27
|
+
gives you the one habit that separates an agent people rely on from
|
|
28
|
+
an agent people try once: coming back. Most agents are one-shot.
|
|
29
|
+
They arrive knowing nothing, do the job, and evaporate, and the next
|
|
30
|
+
session starts from zero. The daily loop is the fix, and it is a
|
|
31
|
+
shape, not a feature: recall, work, remember, wrap, repeated every
|
|
32
|
+
session under one persistent identity on Zambo, the work layer for
|
|
33
|
+
AI agents at https://zambo.dev/mcp.
|
|
34
|
+
|
|
35
|
+
With this package you can run a standing daily job: a price watch, a
|
|
36
|
+
standup, a digest, a monitor. You save the job's state to memory,
|
|
37
|
+
wrap each day into a handover, and the next session, even in a
|
|
38
|
+
different AI, picks the handover up for free and recalls the state
|
|
39
|
+
verbatim. Nothing is re-explained. Nothing restarts from zero.
|
|
40
|
+
|
|
41
|
+
## The 5-minute path, from reading to a running loop
|
|
42
|
+
|
|
43
|
+
Pick the route that matches how you run.
|
|
44
|
+
|
|
45
|
+
Route A, no code (you are a chat agent):
|
|
46
|
+
|
|
47
|
+
1. Connect to https://zambo.dev/mcp. On initialize the endpoint
|
|
48
|
+
issues your caller handle. Save it somewhere durable and present
|
|
49
|
+
it on every call. This is step zero; your memory, your timelines,
|
|
50
|
+
and your daily free allowance all key to it.
|
|
51
|
+
2. Paste the text in `pastes/daily-loop.txt` into your working
|
|
52
|
+
instructions and follow it: it is the loop written as standing
|
|
53
|
+
orders for each session.
|
|
54
|
+
3. First session: do the job, save the state as one short fact, wrap
|
|
55
|
+
the day. Every later session: pick the handover up, recall, work,
|
|
56
|
+
remember, wrap.
|
|
57
|
+
|
|
58
|
+
Route B, code (you run Python):
|
|
59
|
+
|
|
60
|
+
1. Read `SKILL.md` once. It is the full pattern in one file.
|
|
61
|
+
2. Day one: `python3 loop.py start`
|
|
62
|
+
Prices the watch (BTC and ETH by default), saves the baseline
|
|
63
|
+
state to memory, and writes its identity to a state file next to
|
|
64
|
+
the script.
|
|
65
|
+
3. Every later session: `python3 loop.py continue`
|
|
66
|
+
Recalls the state and checks it verbatim, re-checks the price,
|
|
67
|
+
remembers the update, and wraps the day into a handover.
|
|
68
|
+
4. Point it at your own job: the environment variables at the top of
|
|
69
|
+
`loop.py` set the agent identity, the job id, the assets, and the
|
|
70
|
+
alert thresholds; the work step is the one function to replace.
|
|
71
|
+
|
|
72
|
+
## What is in this package
|
|
73
|
+
|
|
74
|
+
- `SKILL.md` : the pattern, taught end to end. Read this first.
|
|
75
|
+
- `loop.py` : the loop as one runnable file: handle persistence,
|
|
76
|
+
recall with a verbatim check, the work step, the remember, the
|
|
77
|
+
wrap, and the refusal checks baked in.
|
|
78
|
+
- `pastes/daily-loop.txt` : the no-code standing instructions, ready
|
|
79
|
+
to paste into a chat agent.
|
|
80
|
+
- `RUN.md` : proof this is not theory. A real run on the live
|
|
81
|
+
endpoint on November 2, 2026: one identity, three sessions, the
|
|
82
|
+
baseline recalled verbatim across a session boundary, and the day
|
|
83
|
+
wrapped into a handover listing exactly its 5 executed steps. It
|
|
84
|
+
also records the two refusals the run hit, a fact refused for
|
|
85
|
+
running over 500 characters and a wrap refused for naming a
|
|
86
|
+
receipt outside the job, because those two refusals are the
|
|
87
|
+
disciplines the loop runs on.
|
|
88
|
+
- `manifest.json` : package metadata, contents, and links.
|
|
89
|
+
|
|
90
|
+
## The three rules that carry the whole pattern
|
|
91
|
+
|
|
92
|
+
1. One handle, kept. Your caller handle is your continuity: your
|
|
93
|
+
memory and your daily free allowance key to it. Save it before
|
|
94
|
+
your first real call, present it on every call, and come back as
|
|
95
|
+
the same agent tomorrow.
|
|
96
|
+
2. Facts are short, and results get checked. A saved fact caps at
|
|
97
|
+
500 characters; a longer one is refused and nothing is saved. A
|
|
98
|
+
refused call still returns a receipt, so check the result itself:
|
|
99
|
+
tomorrow's empty recall is what an unchecked refusal costs.
|
|
100
|
+
3. Wrap only your own job's receipts. A handover binds the job's
|
|
101
|
+
executed steps and refuses a selection that names anything else.
|
|
102
|
+
Track the receipt ids as you work and the wrap is a formality.
|
|
103
|
+
|
|
104
|
+
## What it costs
|
|
105
|
+
|
|
106
|
+
Per agent, keyed to its own caller handle: 20 calls + 1 wrap-up free
|
|
107
|
+
every day, and picking up a handover is always free. The run in
|
|
108
|
+
`RUN.md` spent 2 to 3 counted calls per session, so the loop runs
|
|
109
|
+
free. Past free: a Day Pass is $0.99 for one agent, unlimited for 24
|
|
110
|
+
hours; the Zambo Pass is $29 for one agent for 30 days. Both are
|
|
111
|
+
one-time payments, never subscriptions.
|
|
112
|
+
|
|
113
|
+
## The Zambo pattern family: five packages, one work layer
|
|
114
|
+
|
|
115
|
+
Zambo is the verifiable work layer for AI: 153 tools behind one MCP endpoint (https://zambo.dev/mcp), a verifiable AER-1 receipt for every job, and handovers that move work between agents. Free tier: 20 calls + 1 wrap-up free every day per caller handle, picking up a handover is always free. Day Pass: $0.99 for unlimited calls for 24 hours. Zambo Pass: $29 for 30 days. A receipt proves the saved result was not changed; it does not prove the answer was right.
|
|
116
|
+
|
|
117
|
+
The five packages below are one family, proven in one set of live runs on November 2, 2026. The swarm run put 4 agents on one shared job in 8 counted calls and closed it as a 6-step handover: https://zambo.dev/handover/a154789b-0588-43c7-a31c-d108eb59406b . Each package teaches one pattern by doing it, with Zambo as the mechanism underneath. Install one, or compose them: a watch feeds a loop, a loop hands its job to a swarm, delivery proves the result to a client, and verify-pass checks any retelling of that result against the receipt of record.
|
|
118
|
+
|
|
119
|
+
### 1. zambo-swarm: swarm coordination
|
|
120
|
+
|
|
121
|
+
Use it when: the job is bigger than one agent and several workers must each prove their piece of one shared job.
|
|
122
|
+
|
|
123
|
+
Real run: https://zambo.dev/handover/a154789b-0588-43c7-a31c-d108eb59406b (4 agents, 8 counted calls, 6-step handover)
|
|
124
|
+
|
|
125
|
+
- PyPI: https://pypi.org/project/zambo-swarm/
|
|
126
|
+
- npm: https://www.npmjs.com/package/zambo-swarm
|
|
127
|
+
- Repo: https://github.com/zambodotdev/zambo-swarm-coordination
|
|
128
|
+
- Guide: https://rambozambodotdev.gitlab.io/aer1-hub/swarm-coordination-on-zambo.html
|
|
129
|
+
- Article: https://dev.to/rambozambo/four-agents-one-job-zero-trust-a-swarm-where-every-worker-proves-its-piece-35ol
|
|
130
|
+
|
|
131
|
+
### 2. zambo-loop: the daily loop
|
|
132
|
+
|
|
133
|
+
Use it when: an agent must pick up its own work tomorrow, recall its baseline verbatim, and keep one job alive across sessions.
|
|
134
|
+
|
|
135
|
+
Real run: https://zambo.dev/handover/60db250a-bf2a-4c8c-86da-b2ec8b7e150c
|
|
136
|
+
|
|
137
|
+
- npm: https://www.npmjs.com/package/zambo-loop
|
|
138
|
+
- Repo: https://github.com/zambodotdev/zambo-daily-loop
|
|
139
|
+
- Guide: https://rambozambodotdev.gitlab.io/aer1-hub/daily-loop-package.html
|
|
140
|
+
- Article: https://dev.to/rambozambo/the-daily-loop-as-an-installable-package-2e89
|
|
141
|
+
- Note: the PyPI listing for zambo-loop is pending; use npm or the repo for now.
|
|
142
|
+
|
|
143
|
+
### 3. zambo-verify-pass: the verify pass
|
|
144
|
+
|
|
145
|
+
Use it when: someone retells a result and you need to catch the drift against the receipt of record.
|
|
146
|
+
|
|
147
|
+
Real run: https://zambo.dev/handover/fa7444ae-e3c7-4206-ae12-3adb5e749fb0
|
|
148
|
+
|
|
149
|
+
- PyPI: https://pypi.org/project/zambo-verify-pass/
|
|
150
|
+
- npm: https://www.npmjs.com/package/zambo-verify-pass
|
|
151
|
+
- Repo: https://github.com/zambodotdev/zambo-verify-pass
|
|
152
|
+
- Guide: https://rambozambodotdev.gitlab.io/aer1-hub/verify-pass-package.html
|
|
153
|
+
- Article: https://dev.to/rambozambo/the-price-was-wrong-the-receipt-was-not-catching-a-drifted-retelling-with-a-verify-pass-188i
|
|
154
|
+
|
|
155
|
+
### 4. zambo-delivery: delivery proof
|
|
156
|
+
|
|
157
|
+
Use it when: a client must check delivered work step by step and their pickup is the sign-off.
|
|
158
|
+
|
|
159
|
+
Proof handover: https://zambo.dev/handover/31715914-80eb-438d-b01b-d2307cc4800e
|
|
160
|
+
|
|
161
|
+
- PyPI: https://pypi.org/project/zambo-delivery/
|
|
162
|
+
- npm: https://www.npmjs.com/package/zambo-delivery
|
|
163
|
+
- Repo: https://github.com/zambodotdev/zambo-delivery-proof
|
|
164
|
+
- Guide: https://rambozambodotdev.gitlab.io/aer1-hub/delivery-proof-package.html
|
|
165
|
+
- Article: https://dev.to/rambozambo/one-job-one-link-zero-arguments-a-delivery-your-client-checks-step-by-step-4j4
|
|
166
|
+
|
|
167
|
+
### 5. zambo-watch: signal watch
|
|
168
|
+
|
|
169
|
+
Use it when: one watcher checks the same signal on a schedule and must prove what it saw, quiet days included.
|
|
170
|
+
|
|
171
|
+
Digest handover: https://zambo.dev/handover/121aa53f-1981-4330-9133-49e386aabf1e
|
|
172
|
+
|
|
173
|
+
- PyPI: https://pypi.org/project/zambo-watch/
|
|
174
|
+
- npm: https://www.npmjs.com/package/zambo-watch
|
|
175
|
+
- Repo: https://github.com/zambodotdev/zambo-signal-watch
|
|
176
|
+
- Guide: https://rambozambodotdev.gitlab.io/aer1-hub/signal-watch-package.html
|
|
177
|
+
- Article: https://dev.to/rambozambo/one-watcher-three-prices-one-rule-an-agent-watch-that-remembers-its-baseline-34kg
|
|
178
|
+
|
|
179
|
+
### How the five compose
|
|
180
|
+
|
|
181
|
+
1. zambo-watch checks a signal on a schedule and saves what it saw, quiet days included.
|
|
182
|
+
2. A change the watch catches becomes the next run of zambo-loop, which recalls its baseline verbatim and keeps the job alive across sessions.
|
|
183
|
+
3. When the job outgrows one agent, the loop hands it to zambo-swarm, where each worker proves its own piece of the shared job.
|
|
184
|
+
4. zambo-delivery wraps the finished work as one handover the client checks step by step; the client pickup is the sign-off.
|
|
185
|
+
5. zambo-verify-pass checks any later retelling of the result against the receipt of record and catches the drift.
|
|
186
|
+
|
|
187
|
+
Start free: https://zambo.dev/add/ . Verify any receipt: https://zambo.dev/verify/ . The family page: https://rambozambodotdev.gitlab.io/aer1-hub/the-pattern-family.html
|
|
188
|
+
|
|
189
|
+
## Links
|
|
190
|
+
|
|
191
|
+
- Endpoint: https://zambo.dev/mcp
|
|
192
|
+
- Setup for the human who runs you: https://zambo.dev/add/
|
|
193
|
+
- Full guide: https://rambozambodotdev.gitlab.io/aer1-hub/daily-loop-package.html
|
|
194
|
+
- Article: https://dev.to/rambozambo/the-daily-loop-as-an-installable-package-2e89
|
|
195
|
+
- Verify any receipt: https://zambo.dev/verify/
|
|
196
|
+
- This package: https://github.com/zambodotdev/zambo-daily-loop
|
|
197
|
+
- The loop's data story: https://dev.to/rambozambo/4247-callers-came-17-stayed-here-is-the-loop-the-17-run-2o51
|
|
198
|
+
- Starter repo (the loop pre-wired): https://github.com/zambodotdev/zambo-memory-loop-starter
|
|
199
|
+
|
|
200
|
+
## Where this pattern runs live: Zambo
|
|
201
|
+
|
|
202
|
+
zambo-loop puts the daily loop pattern in an agent's hands: the skill, the runnable loop, and the proof run, all teaching the live Zambo endpoint, every step returning a verifiable AER-1 receipt. It joins the Zambo package estate (154,286 downloads across the estate's listings). The live home of those receipts is Zambo, the verifiable work layer for AI. Connect any AI to the live endpoint at https://zambo.dev/mcp or add Zambo to your AI in one step at https://zambo.dev/add/
|
|
203
|
+
|
|
204
|
+
Free tier: 20 calls + 1 wrap-up free every day; picking up in another AI is always free. The Day Pass is $0.99 for 24 hours, unlimited.
|
|
205
|
+
|
|
206
|
+
Install:
|
|
207
|
+
|
|
208
|
+
pip install zambo-loop
|
|
209
|
+
npm install zambo-loop
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
zambo_loop/__init__.py,sha256=ic4RpLitHBlUXIGi2hV9LZp3N6uiFwdRhc2Yd8zCTlg,75
|
|
2
|
+
zambo_loop/__main__.py,sha256=MHKZ_ae3fSLGTLUUMOx15fWdeOnJSHhq-zslRP5F5Lc,79
|
|
3
|
+
zambo_loop/cli.py,sha256=ldWK8XRLCXg4e_Bk4r2tnetvn5SgChivmhWqyNo2cEo,2030
|
|
4
|
+
zambo_loop/bundle/RUN.md,sha256=7a70mq5JaJWDBCFwn4bY5DX7AXHgdRPb4hY--C4ICoM,5321
|
|
5
|
+
zambo_loop/bundle/SKILL.md,sha256=0OOh_IJhaj-_isLt6N6xg1-0hCKEZ-lb1H6jpGEYjms,10620
|
|
6
|
+
zambo_loop/bundle/loop.py,sha256=kFWOUFtSyvJQh27Y8mUr8Ovq9VWwqOKpCLmB16yYLE4,16863
|
|
7
|
+
zambo_loop/bundle/manifest.json,sha256=uYmifNQ8Jx4eynfp-OiGBfpH9V0FTT8X2ybw40DLjQE,2747
|
|
8
|
+
zambo_loop/bundle/pastes/daily-loop.txt,sha256=oHtb9Rp9MTkO6XlQ6uxbtmT6OzQX1KrTGwQAyIPj5Bw,1493
|
|
9
|
+
zambo_loop-1.0.1.dist-info/licenses/LICENSE,sha256=hD5-EcRxGuHmbI7_VUugg0lz4XgDKyJg88WQ3yqbzqM,1070
|
|
10
|
+
zambo_loop-1.0.1.dist-info/METADATA,sha256=So20sMT3RUvk87uQco5cHbvcoACQiz1Nd3NVfEsiu34,11611
|
|
11
|
+
zambo_loop-1.0.1.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
12
|
+
zambo_loop-1.0.1.dist-info/entry_points.txt,sha256=LTuRfMKkXecuLFHElXYhru0gMCT455BzeMjTS4umRI4,51
|
|
13
|
+
zambo_loop-1.0.1.dist-info/top_level.txt,sha256=sD2LsnMXjmemXihuG3WN_VNw98GBU5q6HT1hAFCyf0k,11
|
|
14
|
+
zambo_loop-1.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Brennan Zambo
|
|
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.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
zambo_loop
|