kredisco 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,324 @@
1
+ Metadata-Version: 2.4
2
+ Name: kredisco
3
+ Version: 0.1.0
4
+ Summary: A trust layer for AI agents
5
+ Author: Rahul Sah
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://kredisco.com
8
+ Project-URL: Source, https://github.com/kredisco/Kredisco
9
+ Keywords: ai,agents,trust,reputation,llm,langgraph
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: requests>=2.28
13
+ Requires-Dist: cryptography>=41.0
14
+
15
+ # Kredisco
16
+
17
+ **A trust layer for AI agents.**
18
+
19
+ Humans carry a credit score. It follows you between lenders, it is held
20
+ by a bureau rather than by you, and it is built from what other parties
21
+ report about your behaviour. A landlord who has never met you can decide
22
+ whether to trust you in seconds.
23
+
24
+ AI agents have no equivalent. You wire one into your pipeline and have
25
+ no idea whether it is dependable until something breaks.
26
+
27
+ Kredisco gives every agent a portable trust score, 300 to 850, earned
28
+ from its real track record and held where the agent cannot reach it.
29
+
30
+ ---
31
+
32
+ ## Why agents need one
33
+
34
+ A pipeline with six agents fails quietly. One step degrades, output
35
+ quality drops, and you find out from a user complaint days later.
36
+ Nothing tells you which agent to stop calling.
37
+
38
+ The instinct is to ask the agents, or to collect ratings. Neither
39
+ survives contact with incentives. An agent will always report that it
40
+ did well. Open rating systems get farmed: an audit of ERC-8004's on-chain
41
+ reputation registry found the large majority of reviewers exhibited
42
+ coordinated Sybil behaviour, and after removing them most rated agents
43
+ had no valid feedback left.
44
+
45
+ Credit scoring solved this problem a long time ago, and not with better
46
+ surveys. It solved it structurally: **the party being scored is never
47
+ the party reporting.** Your bank reports your payments. You cannot file
48
+ your own. You cannot edit the file. You cannot see the number until
49
+ someone pulls it.
50
+
51
+ Kredisco applies that structure to agents.
52
+
53
+ ## What the score is made of
54
+
55
+ Like FICO, the score is a weighted blend of factors, and like FICO,
56
+ every one of them comes from somewhere the scored party cannot reach.
57
+
58
+ | Factor | Weight | Where it comes from |
59
+ |---|---:|---|
60
+ | Rework rate (inverted) | 0.45 | You retried, reassigned, or the next step errored |
61
+ | On-time rate | 0.20 | Measured by the SDK wrapping the call |
62
+ | Counterparty diversity | 0.20 | How many distinct organizations hired it |
63
+ | History depth | 0.15 | Volume of recorded work |
64
+
65
+ Scores run 300–850. A new agent scores 300, so abandoning a damaged
66
+ identity means abandoning everything it earned.
67
+
68
+ Rework carries the most weight because it is hardest to fake. An agent
69
+ cannot retry itself, cannot stop a downstream step from failing, and
70
+ cannot stop you from routing around it.
71
+
72
+ ## Who reports, and why it can't be gamed
73
+
74
+ A lender reports your payment to the bureau. The bureau holds the file.
75
+ A future lender queries the bureau, never you.
76
+
77
+ Kredisco works the same way. Your orchestrator is the reporter, Kredisco
78
+ is the bureau, and the agent is the subject:
79
+
80
+ 1. Your agent completes a task
81
+ 2. The agent signs a record of it; your orchestrator countersigns
82
+ 3. Your orchestrator submits it, authenticated with your API key
83
+ 4. Kredisco verifies both signatures and files it under your organization
84
+ 5. The score is recomputed from everything on file
85
+
86
+ Both signatures are required, so neither side can act alone. An agent
87
+ cannot manufacture a history, because every entry needs a real
88
+ counterparty to have signed it. And it never holds its own score — when
89
+ someone wants to know whether to trust it, they ask Kredisco.
90
+
91
+ ---
92
+
93
+ ## Quickstart
94
+
95
+ ### 1. Get a key
96
+
97
+ Sign in with GitHub at the Kredisco dashboard and create an API key. One
98
+ key covers everything you build.
99
+
100
+ ```bash
101
+ export KREDISCO_API_KEY=kd_...
102
+ ```
103
+
104
+ ### 2. Install
105
+
106
+ ```bash
107
+ pip install kredisco
108
+ ```
109
+
110
+ ### 3. Connect
111
+
112
+ ```python
113
+ import os
114
+ from kredisco import Kredisco
115
+
116
+ kd = Kredisco(api_key=os.environ["KREDISCO_API_KEY"])
117
+ ```
118
+
119
+ ### 4. Name each agent you want scored
120
+
121
+ An "agent" is any component you call and could imagine replacing: a
122
+ model behind a prompt, a third-party API, a tool, a subprocess. Give it
123
+ a name once. Kredisco creates its identity and reuses it forever after.
124
+
125
+ ```python
126
+ extractor = kd.agent("invoice-extractor")
127
+ reviewer = kd.agent("code-reviewer")
128
+ ```
129
+
130
+ ### 5. Route your calls through `track`
131
+
132
+ Take the call you already make:
133
+
134
+ ```python
135
+ data = extract_invoice(pdf)
136
+ ```
137
+
138
+ Hand the function to `track` instead of calling it yourself:
139
+
140
+ ```python
141
+ data = kd.track(extractor, "extract", extract_invoice, pdf)
142
+ ```
143
+
144
+ Four arguments: the agent, a label for the kind of work, **the function
145
+ without parentheses**, then its arguments.
146
+
147
+ Kredisco calls the function, times it, files a record, and hands back
148
+ exactly what your function returned. If it raises, the failure is
149
+ recorded and the exception propagates normally. Your logic does not
150
+ change.
151
+
152
+ That is the integration. Scores start appearing on the dashboard.
153
+
154
+ ---
155
+
156
+ ## Telling Kredisco what counts as a failure
157
+
158
+ By default a task fails only if your function raises.
159
+
160
+ Most pipelines have a stricter rule — a required field, a schema, a
161
+ minimum length. Pass `validate` and Kredisco uses yours:
162
+
163
+ ```python
164
+ data = kd.track(
165
+ extractor, "extract", extract_invoice, pdf,
166
+ validate=lambda d: d is not None and "total" in d,
167
+ )
168
+ ```
169
+
170
+ **This matters more than it looks.** With no validator and nothing
171
+ raising, every task passes, and the largest factor in the score is
172
+ measuring nothing. Kredisco is worth most to pipelines that already
173
+ check their own output.
174
+
175
+ ## Retries
176
+
177
+ Add `retries=1` and a failed attempt runs again. Kredisco links the
178
+ retry to the original task, and that link is what drives the rework
179
+ factor:
180
+
181
+ ```python
182
+ data = kd.track(
183
+ extractor, "extract", extract_invoice, pdf,
184
+ validate=lambda d: d is not None and "total" in d,
185
+ retries=1,
186
+ )
187
+ ```
188
+
189
+ If your orchestrator already retries on its own, leave this off and use
190
+ the context manager below for each attempt.
191
+
192
+ ## When everything fails
193
+
194
+ If every attempt fails, `track` re-raises the last exception. Sometimes
195
+ that is what you want. Often it is not — a graph node that stops the
196
+ whole run because one step returned bad JSON is worse than a node that
197
+ carries on with an empty value.
198
+
199
+ Pass `default` and `track` returns it instead of raising:
200
+
201
+ ```python
202
+ data = kd.track(
203
+ extractor, "extract", extract_invoice, pdf,
204
+ validate=lambda d: d is not None and "total" in d,
205
+ retries=1,
206
+ default={},
207
+ )
208
+ ```
209
+
210
+ The failure is still recorded and the score still drops. Only your
211
+ control flow changes.
212
+
213
+ Without `default`, every call site needs its own `try`/`except`. With
214
+ it, a pipeline step is one expression:
215
+
216
+ ```python
217
+ def extract_node(state):
218
+ return {"fields": kd.track(
219
+ extractor, "extract", extract_invoice, state["pdf"],
220
+ validate=fields_ok, retries=1, default={},
221
+ )}
222
+ ```
223
+
224
+ ## When you cannot hand over a function
225
+
226
+ Some code will not collapse into a single call — the body of a graph
227
+ node, a streamed response consumed in pieces, a block with setup and
228
+ teardown around it. Open a task and report the outcome yourself:
229
+
230
+ ```python
231
+ with kd.task(reviewer, "review") as t:
232
+ findings = run_review(diff)
233
+ t.accepted = len(findings) > 0
234
+ ```
235
+
236
+ The clock starts when the block opens and stops when it closes. An
237
+ exception inside records a failure and re-raises.
238
+
239
+ ## Grouping by pipeline
240
+
241
+ If you run more than one pipeline, tag each one. Same key, different
242
+ label — they appear as separate groups on the dashboard but roll up to
243
+ one organization.
244
+
245
+ ```python
246
+ kd = Kredisco(
247
+ api_key=os.environ["KREDISCO_API_KEY"],
248
+ workflow_id="invoice-ingest",
249
+ )
250
+ ```
251
+
252
+ ## Reading scores
253
+
254
+ ```python
255
+ kd.score(extractor.pubkey) # one number, 300–850
256
+ kd.breakdown(extractor.pubkey) # what the score is made of
257
+ kd.dashboard() # your agents, grouped by pipeline
258
+ kd.best("extract", minimum=650) # highest scorer for a kind of work
259
+ ```
260
+
261
+ `best` is the point of the whole thing: pick who does the work by track
262
+ record instead of hardcoding a vendor and hoping.
263
+
264
+ ## Things worth knowing
265
+
266
+ **Reporting never breaks your pipeline.** If Kredisco is unreachable or
267
+ your key is rejected, the failure is logged and your work continues.
268
+
269
+ ```python
270
+ import logging
271
+ logging.getLogger("kredisco").setLevel(logging.INFO)
272
+ ```
273
+
274
+ **Agent keys live on disk.** Kredisco writes each agent's keypair to
275
+ `.kredisco/`. That directory *is* your agents' identity — lose it and
276
+ every score resets to 300. Back it up, and add it to `.gitignore`.
277
+
278
+ **Timestamps come from the SDK**, not from your agent, so an agent
279
+ cannot shorten its own recorded duration.
280
+
281
+ ---
282
+
283
+ ---
284
+
285
+ ## A working example
286
+
287
+ [`examples/langgraph_support_triage.py`](examples/langgraph_support_triage.py)
288
+ is a four-agent LangGraph pipeline built on two different Claude models,
289
+ with a validator on every step.
290
+
291
+ Run it a few times. The scores separate for real reasons — one agent
292
+ returns JSON wrapped in code fences, another hits a response shape the
293
+ caller did not expect, a third is simply slower. Fix the prompt that is
294
+ failing and watch that agent climb over subsequent runs, without ever
295
+ catching the agent that never failed.
296
+
297
+ That gap is the point. History has weight, and recovery takes work.
298
+
299
+ ---
300
+
301
+ ## What this does not solve
302
+
303
+ **A dishonest reporter can fabricate everything.** Signatures prove who
304
+ signed, not that the content is true. Credit bureaus have the same hole
305
+ and handle it with licensing and liability rather than cryptography.
306
+ Kredisco's mitigation is that keys are tied to GitHub accounts and the
307
+ diversity factor requires many distinct organizations.
308
+
309
+ **Identity is still cheap.** A bad score can be abandoned for a fresh
310
+ keypair. Starting at the floor makes that costly, not impossible.
311
+
312
+ **One score across all task types.** An agent good at summarising and
313
+ bad at code review averages into noise.
314
+
315
+ **Rate limiting is per-process** and does not survive horizontal
316
+ scaling.
317
+
318
+ **No test suite.** The scoring model has changed several times and was
319
+ verified by inspection.
320
+
321
+ ---
322
+
323
+ [![LinkedIn](https://img.shields.io/badge/LinkedIn-0A66C2?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/in/reachrshah)
324
+ [![GitHub](https://img.shields.io/badge/GitHub-14161A?style=for-the-badge&logo=github&logoColor=white)](https://github.com/idfwyy)
@@ -0,0 +1,310 @@
1
+ # Kredisco
2
+
3
+ **A trust layer for AI agents.**
4
+
5
+ Humans carry a credit score. It follows you between lenders, it is held
6
+ by a bureau rather than by you, and it is built from what other parties
7
+ report about your behaviour. A landlord who has never met you can decide
8
+ whether to trust you in seconds.
9
+
10
+ AI agents have no equivalent. You wire one into your pipeline and have
11
+ no idea whether it is dependable until something breaks.
12
+
13
+ Kredisco gives every agent a portable trust score, 300 to 850, earned
14
+ from its real track record and held where the agent cannot reach it.
15
+
16
+ ---
17
+
18
+ ## Why agents need one
19
+
20
+ A pipeline with six agents fails quietly. One step degrades, output
21
+ quality drops, and you find out from a user complaint days later.
22
+ Nothing tells you which agent to stop calling.
23
+
24
+ The instinct is to ask the agents, or to collect ratings. Neither
25
+ survives contact with incentives. An agent will always report that it
26
+ did well. Open rating systems get farmed: an audit of ERC-8004's on-chain
27
+ reputation registry found the large majority of reviewers exhibited
28
+ coordinated Sybil behaviour, and after removing them most rated agents
29
+ had no valid feedback left.
30
+
31
+ Credit scoring solved this problem a long time ago, and not with better
32
+ surveys. It solved it structurally: **the party being scored is never
33
+ the party reporting.** Your bank reports your payments. You cannot file
34
+ your own. You cannot edit the file. You cannot see the number until
35
+ someone pulls it.
36
+
37
+ Kredisco applies that structure to agents.
38
+
39
+ ## What the score is made of
40
+
41
+ Like FICO, the score is a weighted blend of factors, and like FICO,
42
+ every one of them comes from somewhere the scored party cannot reach.
43
+
44
+ | Factor | Weight | Where it comes from |
45
+ |---|---:|---|
46
+ | Rework rate (inverted) | 0.45 | You retried, reassigned, or the next step errored |
47
+ | On-time rate | 0.20 | Measured by the SDK wrapping the call |
48
+ | Counterparty diversity | 0.20 | How many distinct organizations hired it |
49
+ | History depth | 0.15 | Volume of recorded work |
50
+
51
+ Scores run 300–850. A new agent scores 300, so abandoning a damaged
52
+ identity means abandoning everything it earned.
53
+
54
+ Rework carries the most weight because it is hardest to fake. An agent
55
+ cannot retry itself, cannot stop a downstream step from failing, and
56
+ cannot stop you from routing around it.
57
+
58
+ ## Who reports, and why it can't be gamed
59
+
60
+ A lender reports your payment to the bureau. The bureau holds the file.
61
+ A future lender queries the bureau, never you.
62
+
63
+ Kredisco works the same way. Your orchestrator is the reporter, Kredisco
64
+ is the bureau, and the agent is the subject:
65
+
66
+ 1. Your agent completes a task
67
+ 2. The agent signs a record of it; your orchestrator countersigns
68
+ 3. Your orchestrator submits it, authenticated with your API key
69
+ 4. Kredisco verifies both signatures and files it under your organization
70
+ 5. The score is recomputed from everything on file
71
+
72
+ Both signatures are required, so neither side can act alone. An agent
73
+ cannot manufacture a history, because every entry needs a real
74
+ counterparty to have signed it. And it never holds its own score — when
75
+ someone wants to know whether to trust it, they ask Kredisco.
76
+
77
+ ---
78
+
79
+ ## Quickstart
80
+
81
+ ### 1. Get a key
82
+
83
+ Sign in with GitHub at the Kredisco dashboard and create an API key. One
84
+ key covers everything you build.
85
+
86
+ ```bash
87
+ export KREDISCO_API_KEY=kd_...
88
+ ```
89
+
90
+ ### 2. Install
91
+
92
+ ```bash
93
+ pip install kredisco
94
+ ```
95
+
96
+ ### 3. Connect
97
+
98
+ ```python
99
+ import os
100
+ from kredisco import Kredisco
101
+
102
+ kd = Kredisco(api_key=os.environ["KREDISCO_API_KEY"])
103
+ ```
104
+
105
+ ### 4. Name each agent you want scored
106
+
107
+ An "agent" is any component you call and could imagine replacing: a
108
+ model behind a prompt, a third-party API, a tool, a subprocess. Give it
109
+ a name once. Kredisco creates its identity and reuses it forever after.
110
+
111
+ ```python
112
+ extractor = kd.agent("invoice-extractor")
113
+ reviewer = kd.agent("code-reviewer")
114
+ ```
115
+
116
+ ### 5. Route your calls through `track`
117
+
118
+ Take the call you already make:
119
+
120
+ ```python
121
+ data = extract_invoice(pdf)
122
+ ```
123
+
124
+ Hand the function to `track` instead of calling it yourself:
125
+
126
+ ```python
127
+ data = kd.track(extractor, "extract", extract_invoice, pdf)
128
+ ```
129
+
130
+ Four arguments: the agent, a label for the kind of work, **the function
131
+ without parentheses**, then its arguments.
132
+
133
+ Kredisco calls the function, times it, files a record, and hands back
134
+ exactly what your function returned. If it raises, the failure is
135
+ recorded and the exception propagates normally. Your logic does not
136
+ change.
137
+
138
+ That is the integration. Scores start appearing on the dashboard.
139
+
140
+ ---
141
+
142
+ ## Telling Kredisco what counts as a failure
143
+
144
+ By default a task fails only if your function raises.
145
+
146
+ Most pipelines have a stricter rule — a required field, a schema, a
147
+ minimum length. Pass `validate` and Kredisco uses yours:
148
+
149
+ ```python
150
+ data = kd.track(
151
+ extractor, "extract", extract_invoice, pdf,
152
+ validate=lambda d: d is not None and "total" in d,
153
+ )
154
+ ```
155
+
156
+ **This matters more than it looks.** With no validator and nothing
157
+ raising, every task passes, and the largest factor in the score is
158
+ measuring nothing. Kredisco is worth most to pipelines that already
159
+ check their own output.
160
+
161
+ ## Retries
162
+
163
+ Add `retries=1` and a failed attempt runs again. Kredisco links the
164
+ retry to the original task, and that link is what drives the rework
165
+ factor:
166
+
167
+ ```python
168
+ data = kd.track(
169
+ extractor, "extract", extract_invoice, pdf,
170
+ validate=lambda d: d is not None and "total" in d,
171
+ retries=1,
172
+ )
173
+ ```
174
+
175
+ If your orchestrator already retries on its own, leave this off and use
176
+ the context manager below for each attempt.
177
+
178
+ ## When everything fails
179
+
180
+ If every attempt fails, `track` re-raises the last exception. Sometimes
181
+ that is what you want. Often it is not — a graph node that stops the
182
+ whole run because one step returned bad JSON is worse than a node that
183
+ carries on with an empty value.
184
+
185
+ Pass `default` and `track` returns it instead of raising:
186
+
187
+ ```python
188
+ data = kd.track(
189
+ extractor, "extract", extract_invoice, pdf,
190
+ validate=lambda d: d is not None and "total" in d,
191
+ retries=1,
192
+ default={},
193
+ )
194
+ ```
195
+
196
+ The failure is still recorded and the score still drops. Only your
197
+ control flow changes.
198
+
199
+ Without `default`, every call site needs its own `try`/`except`. With
200
+ it, a pipeline step is one expression:
201
+
202
+ ```python
203
+ def extract_node(state):
204
+ return {"fields": kd.track(
205
+ extractor, "extract", extract_invoice, state["pdf"],
206
+ validate=fields_ok, retries=1, default={},
207
+ )}
208
+ ```
209
+
210
+ ## When you cannot hand over a function
211
+
212
+ Some code will not collapse into a single call — the body of a graph
213
+ node, a streamed response consumed in pieces, a block with setup and
214
+ teardown around it. Open a task and report the outcome yourself:
215
+
216
+ ```python
217
+ with kd.task(reviewer, "review") as t:
218
+ findings = run_review(diff)
219
+ t.accepted = len(findings) > 0
220
+ ```
221
+
222
+ The clock starts when the block opens and stops when it closes. An
223
+ exception inside records a failure and re-raises.
224
+
225
+ ## Grouping by pipeline
226
+
227
+ If you run more than one pipeline, tag each one. Same key, different
228
+ label — they appear as separate groups on the dashboard but roll up to
229
+ one organization.
230
+
231
+ ```python
232
+ kd = Kredisco(
233
+ api_key=os.environ["KREDISCO_API_KEY"],
234
+ workflow_id="invoice-ingest",
235
+ )
236
+ ```
237
+
238
+ ## Reading scores
239
+
240
+ ```python
241
+ kd.score(extractor.pubkey) # one number, 300–850
242
+ kd.breakdown(extractor.pubkey) # what the score is made of
243
+ kd.dashboard() # your agents, grouped by pipeline
244
+ kd.best("extract", minimum=650) # highest scorer for a kind of work
245
+ ```
246
+
247
+ `best` is the point of the whole thing: pick who does the work by track
248
+ record instead of hardcoding a vendor and hoping.
249
+
250
+ ## Things worth knowing
251
+
252
+ **Reporting never breaks your pipeline.** If Kredisco is unreachable or
253
+ your key is rejected, the failure is logged and your work continues.
254
+
255
+ ```python
256
+ import logging
257
+ logging.getLogger("kredisco").setLevel(logging.INFO)
258
+ ```
259
+
260
+ **Agent keys live on disk.** Kredisco writes each agent's keypair to
261
+ `.kredisco/`. That directory *is* your agents' identity — lose it and
262
+ every score resets to 300. Back it up, and add it to `.gitignore`.
263
+
264
+ **Timestamps come from the SDK**, not from your agent, so an agent
265
+ cannot shorten its own recorded duration.
266
+
267
+ ---
268
+
269
+ ---
270
+
271
+ ## A working example
272
+
273
+ [`examples/langgraph_support_triage.py`](examples/langgraph_support_triage.py)
274
+ is a four-agent LangGraph pipeline built on two different Claude models,
275
+ with a validator on every step.
276
+
277
+ Run it a few times. The scores separate for real reasons — one agent
278
+ returns JSON wrapped in code fences, another hits a response shape the
279
+ caller did not expect, a third is simply slower. Fix the prompt that is
280
+ failing and watch that agent climb over subsequent runs, without ever
281
+ catching the agent that never failed.
282
+
283
+ That gap is the point. History has weight, and recovery takes work.
284
+
285
+ ---
286
+
287
+ ## What this does not solve
288
+
289
+ **A dishonest reporter can fabricate everything.** Signatures prove who
290
+ signed, not that the content is true. Credit bureaus have the same hole
291
+ and handle it with licensing and liability rather than cryptography.
292
+ Kredisco's mitigation is that keys are tied to GitHub accounts and the
293
+ diversity factor requires many distinct organizations.
294
+
295
+ **Identity is still cheap.** A bad score can be abandoned for a fresh
296
+ keypair. Starting at the floor makes that costly, not impossible.
297
+
298
+ **One score across all task types.** An agent good at summarising and
299
+ bad at code review averages into noise.
300
+
301
+ **Rate limiting is per-process** and does not survive horizontal
302
+ scaling.
303
+
304
+ **No test suite.** The scoring model has changed several times and was
305
+ verified by inspection.
306
+
307
+ ---
308
+
309
+ [![LinkedIn](https://img.shields.io/badge/LinkedIn-0A66C2?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/in/reachrshah)
310
+ [![GitHub](https://img.shields.io/badge/GitHub-14161A?style=for-the-badge&logo=github&logoColor=white)](https://github.com/idfwyy)
@@ -0,0 +1,5 @@
1
+ from kredisco.client import Kredisco
2
+ from kredisco.identity import Keypair
3
+ from kredisco.receipt import Receipt
4
+
5
+ __all__ = ["Kredisco", "Keypair", "Receipt"]
@@ -0,0 +1,238 @@
1
+ import logging
2
+ import os
3
+ import time
4
+ import uuid
5
+ from contextlib import contextmanager
6
+
7
+ import requests
8
+ from requests.adapters import HTTPAdapter
9
+ from urllib3.util.retry import Retry
10
+
11
+ try:
12
+ from kredisco.identity import Keypair
13
+ from kredisco.receipt import Receipt
14
+ except ModuleNotFoundError:
15
+ from identity import Keypair
16
+ from receipt import Receipt
17
+
18
+ DEFAULT_SERVER = "https://api.kredisco.com"
19
+ DEFAULT_TIMEOUT = 10.0
20
+ DEFAULT_BUDGET = 30.0
21
+ DEFAULT_KEY_DIR = ".kredisco"
22
+ _RAISE = object()
23
+
24
+ logger = logging.getLogger("kredisco")
25
+
26
+
27
+ class KrediscoError(Exception):
28
+ pass
29
+
30
+
31
+ class Agent:
32
+
33
+ def __init__(self, name, specialty, keypair):
34
+ self.name = name
35
+ self.specialty = specialty
36
+ self.keypair = keypair
37
+
38
+ @property
39
+ def pubkey(self):
40
+ return self.keypair.public_key_hex()
41
+
42
+ def __repr__(self):
43
+ return "<Agent {} {}>".format(self.name, self.pubkey[:8])
44
+
45
+
46
+ class Task:
47
+
48
+ def __init__(self):
49
+ self.accepted = True
50
+ self.result = None
51
+ self.error = None
52
+
53
+
54
+ class Kredisco:
55
+
56
+ def __init__(self, api_key=None, workflow_id=None, server=None,
57
+ key_dir=None, timeout=DEFAULT_TIMEOUT, budget=DEFAULT_BUDGET):
58
+ self.api_key = api_key or os.environ.get("KREDISCO_API_KEY")
59
+ if not self.api_key:
60
+ raise KrediscoError(
61
+ "No API key. Pass api_key= or set KREDISCO_API_KEY. "
62
+ "Create one at your Kredisco dashboard."
63
+ )
64
+
65
+ self.workflow_id = workflow_id
66
+ self.server = (server or os.environ.get("KREDISCO_SERVER")
67
+ or DEFAULT_SERVER).rstrip("/")
68
+ self.key_dir = key_dir or os.environ.get("KREDISCO_KEY_DIR", DEFAULT_KEY_DIR)
69
+ self.timeout = timeout
70
+ self.budget = budget
71
+
72
+ self.session = self._build_session()
73
+ self.caller = Keypair.load_or_create(
74
+ os.path.join(self.key_dir, "orchestrator.key")
75
+ )
76
+ self._registered = set()
77
+
78
+ # ---------- plumbing ----------
79
+
80
+ def _build_session(self):
81
+ session = requests.Session()
82
+ session.headers["Authorization"] = "Bearer " + self.api_key
83
+ retry = Retry(
84
+ total=3,
85
+ backoff_factor=0.5,
86
+ status_forcelist=[429, 500, 502, 503, 504],
87
+ allowed_methods=["GET", "POST"],
88
+ respect_retry_after_header=True,
89
+ )
90
+ adapter = HTTPAdapter(max_retries=retry)
91
+ session.mount("http://", adapter)
92
+ session.mount("https://", adapter)
93
+ return session
94
+
95
+ def _post(self, path, payload):
96
+ try:
97
+ res = self.session.post(self.server + path, json=payload,
98
+ timeout=self.timeout)
99
+ except requests.RequestException as exc:
100
+ logger.warning("kredisco: %s unreachable (%s)", path, exc)
101
+ return None
102
+ if res.status_code == 401:
103
+ logger.error("kredisco: API key rejected")
104
+ elif res.status_code >= 400:
105
+ logger.warning("kredisco: %s returned %s %s",
106
+ path, res.status_code, res.text[:200])
107
+ return res
108
+
109
+ def _get(self, path):
110
+ try:
111
+ res = self.session.get(self.server + path, timeout=self.timeout)
112
+ except requests.RequestException as exc:
113
+ raise KrediscoError("Could not reach Kredisco: {}".format(exc))
114
+ if res.status_code >= 400:
115
+ raise KrediscoError("{} returned {}".format(path, res.status_code))
116
+ return res.json()
117
+
118
+ # ---------- agents ----------
119
+
120
+ def agent(self, name, specialty=None):
121
+ keypair = Keypair.load_or_create(
122
+ os.path.join(self.key_dir, "agents", name + ".key")
123
+ )
124
+ agent = Agent(name, specialty or name, keypair)
125
+
126
+ if agent.pubkey not in self._registered:
127
+ self._post("/register", {
128
+ "pubkey": agent.pubkey,
129
+ "name": agent.name,
130
+ "specialty": agent.specialty,
131
+ })
132
+ self._registered.add(agent.pubkey)
133
+
134
+ return agent
135
+
136
+ # ---------- reporting ----------
137
+
138
+ def _report(self, agent, task_type, started_at, delivered_at,
139
+ accepted, deadline, parent_task_id=None):
140
+ task_id = str(uuid.uuid4())
141
+ receipt = Receipt(
142
+ task_id, task_type, deadline, started_at, delivered_at,
143
+ accepted, parent_task_id=parent_task_id,
144
+ )
145
+ receipt.sign_by(agent.keypair, "specialist")
146
+ receipt.sign_by(self.caller, "hiring")
147
+
148
+ self._post("/settle", {
149
+ "task_id": task_id,
150
+ "task_type": task_type,
151
+ "deadline": deadline,
152
+ "started_at": started_at,
153
+ "delivered_at": delivered_at,
154
+ "accepted": accepted,
155
+ "specialist_sig": receipt.specialist_sig,
156
+ "hiring_sig": receipt.hiring_sig,
157
+ "specialist_pubkey": agent.pubkey,
158
+ "hiring_pubkey": self.caller.public_key_hex(),
159
+ "agent_id": agent.pubkey,
160
+ "caller_id": self.caller.public_key_hex(),
161
+ "parent_task_id": parent_task_id,
162
+ "workflow_id": self.workflow_id,
163
+ })
164
+ return task_id
165
+
166
+ def track(self, agent, task_type, fn, *args,
167
+ validate=None, budget=None, retries=0, default=_RAISE, **kwargs):
168
+ parent_task_id = None
169
+ attempts = retries + 1
170
+ last_error = None
171
+
172
+ for attempt in range(attempts):
173
+ started_at = time.time()
174
+ deadline = started_at + (budget or self.budget)
175
+ accepted = True
176
+ result = None
177
+ last_error = None
178
+
179
+ try:
180
+ result = fn(*args, **kwargs)
181
+ if validate is not None:
182
+ accepted = bool(validate(result))
183
+ except Exception as exc:
184
+ accepted = False
185
+ last_error = exc
186
+
187
+ delivered_at = time.time()
188
+ task_id = self._report(agent, task_type, started_at, delivered_at,
189
+ accepted, deadline, parent_task_id)
190
+
191
+ if accepted:
192
+ return result
193
+
194
+ parent_task_id = task_id
195
+ if attempt < attempts - 1:
196
+ logger.info("kredisco: retrying %s on %s", task_type, agent.name)
197
+
198
+ if default is not _RAISE:
199
+ logger.info("kredisco: %s on %s failed, returning default",
200
+ task_type, agent.name)
201
+ return default
202
+
203
+ if last_error is not None:
204
+ raise last_error
205
+ return result
206
+ @contextmanager
207
+ def task(self, agent, task_type, budget=None, parent_task_id=None):
208
+ started_at = time.time()
209
+ deadline = started_at + (budget or self.budget)
210
+ handle = Task()
211
+ try:
212
+ yield handle
213
+ except Exception:
214
+ handle.accepted = False
215
+ self._report(agent, task_type, started_at, time.time(),
216
+ False, deadline, parent_task_id)
217
+ raise
218
+ self._report(agent, task_type, started_at, time.time(),
219
+ handle.accepted, deadline, parent_task_id)
220
+
221
+ # ---------- reading ----------
222
+
223
+ def score(self, pubkey):
224
+ return self._get("/score/" + pubkey)["score"]
225
+
226
+ def breakdown(self, pubkey):
227
+ return self._get("/agent/" + pubkey + "/breakdown")
228
+
229
+ def dashboard(self):
230
+ return self._get("/dashboard")["workflows"]
231
+
232
+ def leaderboard(self):
233
+ return self._get("/leaderboard")["leaderboard"]
234
+
235
+ def best(self, specialty, minimum=0):
236
+ rows = [r for r in self.leaderboard()
237
+ if r.get("specialty") == specialty and r["score"] >= minimum]
238
+ return rows[0] if rows else None
@@ -0,0 +1,76 @@
1
+ import os
2
+ from pathlib import Path
3
+
4
+ from cryptography.exceptions import InvalidSignature
5
+ from cryptography.hazmat.primitives.asymmetric import ed25519
6
+
7
+
8
+ class Keypair:
9
+
10
+ def __init__(self, private_key=None):
11
+ self.private_key = private_key or ed25519.Ed25519PrivateKey.generate()
12
+ self.public_key = self.private_key.public_key()
13
+
14
+ def public_key_hex(self) -> str:
15
+ return self.public_key.public_bytes_raw().hex()
16
+
17
+ def private_key_hex(self) -> str:
18
+ return self.private_key.private_bytes_raw().hex()
19
+
20
+ @classmethod
21
+ def from_hex(cls, private_key_hex: str) -> "Keypair":
22
+ raw = bytes.fromhex(private_key_hex)
23
+ if len(raw) != 32:
24
+ raise ValueError("private key must be 32 bytes")
25
+ return cls(ed25519.Ed25519PrivateKey.from_private_bytes(raw))
26
+
27
+ def save(self, path) -> None:
28
+ path = Path(path)
29
+ path.parent.mkdir(parents=True, exist_ok=True)
30
+ path.write_text(self.private_key_hex())
31
+ try:
32
+ os.chmod(path, 0o600)
33
+ except OSError:
34
+ pass
35
+
36
+ @classmethod
37
+ def load(cls, path) -> "Keypair":
38
+ return cls.from_hex(Path(path).read_text().strip())
39
+
40
+ @classmethod
41
+ def load_or_create(cls, path) -> "Keypair":
42
+ path = Path(path)
43
+ if path.exists():
44
+ try:
45
+ return cls.load(path)
46
+ except (ValueError, OSError):
47
+ pass
48
+ keypair = cls()
49
+ keypair.save(path)
50
+ return keypair
51
+
52
+ def sign(self, message: str) -> str:
53
+ return self.private_key.sign(message.encode()).hex()
54
+
55
+ @staticmethod
56
+ def verify(public_key_hex: str, message: str, signature_hex: str) -> bool:
57
+ if not public_key_hex or not signature_hex or message is None:
58
+ return False
59
+ try:
60
+ raw = bytes.fromhex(public_key_hex)
61
+ key_obj = ed25519.Ed25519PublicKey.from_public_bytes(raw)
62
+ key_obj.verify(bytes.fromhex(signature_hex), message.encode())
63
+ return True
64
+ except (InvalidSignature, ValueError, TypeError):
65
+ return False
66
+
67
+
68
+ if __name__ == "__main__":
69
+ kp = Keypair()
70
+ print("agent ID:", kp.public_key_hex())
71
+ sig = kp.sign("hello")
72
+ print("real:", Keypair.verify(kp.public_key_hex(), "hello", sig))
73
+ print("fake:", Keypair.verify(kp.public_key_hex(), "hacked", sig))
74
+
75
+ restored = Keypair.from_hex(kp.private_key_hex())
76
+ print("restored matches:", restored.public_key_hex() == kp.public_key_hex())
@@ -0,0 +1,57 @@
1
+ import json
2
+ try:
3
+ from kredisco.identity import Keypair
4
+ except ModuleNotFoundError:
5
+ from identity import Keypair
6
+
7
+ class Receipt:
8
+ def __init__(self, task_id, task_type, deadline, started_at, delivered_at, accepted, specialist_sig = None , hiring_sig = None, parent_task_id = None):
9
+ self.task_id = task_id
10
+ self.task_type = task_type
11
+ self.deadline = deadline
12
+ self.started_at = started_at
13
+ self.delivered_at = delivered_at
14
+ self.accepted = accepted
15
+ self.specialist_sig = specialist_sig
16
+ self.hiring_sig = hiring_sig
17
+ self.parent_task_id = parent_task_id
18
+
19
+ def to_text(self) -> str:
20
+ data = {
21
+ "task_id" : self.task_id,
22
+ "task_type" : self.task_type,
23
+ "deadline" : self.deadline,
24
+ "started_at": self.started_at,
25
+ "delivered_at" : self.delivered_at,
26
+ "accepted" : self.accepted
27
+ }
28
+ return json.dumps(data, sort_keys=True)
29
+
30
+ def sign_by(self, keypair, role) -> str:
31
+ sig = keypair.sign(self.to_text())
32
+ if role == "specialist":
33
+ self.specialist_sig = sig
34
+ else:
35
+ self.hiring_sig = sig
36
+ return sig
37
+
38
+
39
+ def verify_signatures(self,specialist_pubkey,hiring_pubkey) -> bool:
40
+ return Keypair.verify( specialist_pubkey , self.to_text() , self.specialist_sig ) and Keypair.verify( hiring_pubkey , self.to_text() , self.hiring_sig )
41
+
42
+
43
+
44
+
45
+ if __name__ == "__main__":
46
+ specialist = Keypair()
47
+ hiring = Keypair()
48
+
49
+ r = Receipt("47", "summarize", 100.0, 90.0, 95.0, True)
50
+ r.sign_by(specialist, "specialist")
51
+ r.sign_by(hiring, "hiring")
52
+
53
+ print("both valid:", r.verify_signatures(specialist.public_key_hex(), hiring.public_key_hex()))
54
+
55
+ fake = Receipt("99", "summarize", 100.0, 90.0, 95.0, True)
56
+ fake.sign_by(specialist, "specialist") # only specialist signs, no hiring countersign
57
+ print("faker valid:", fake.verify_signatures(specialist.public_key_hex(), hiring.public_key_hex()))
@@ -0,0 +1,324 @@
1
+ Metadata-Version: 2.4
2
+ Name: kredisco
3
+ Version: 0.1.0
4
+ Summary: A trust layer for AI agents
5
+ Author: Rahul Sah
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://kredisco.com
8
+ Project-URL: Source, https://github.com/kredisco/Kredisco
9
+ Keywords: ai,agents,trust,reputation,llm,langgraph
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: requests>=2.28
13
+ Requires-Dist: cryptography>=41.0
14
+
15
+ # Kredisco
16
+
17
+ **A trust layer for AI agents.**
18
+
19
+ Humans carry a credit score. It follows you between lenders, it is held
20
+ by a bureau rather than by you, and it is built from what other parties
21
+ report about your behaviour. A landlord who has never met you can decide
22
+ whether to trust you in seconds.
23
+
24
+ AI agents have no equivalent. You wire one into your pipeline and have
25
+ no idea whether it is dependable until something breaks.
26
+
27
+ Kredisco gives every agent a portable trust score, 300 to 850, earned
28
+ from its real track record and held where the agent cannot reach it.
29
+
30
+ ---
31
+
32
+ ## Why agents need one
33
+
34
+ A pipeline with six agents fails quietly. One step degrades, output
35
+ quality drops, and you find out from a user complaint days later.
36
+ Nothing tells you which agent to stop calling.
37
+
38
+ The instinct is to ask the agents, or to collect ratings. Neither
39
+ survives contact with incentives. An agent will always report that it
40
+ did well. Open rating systems get farmed: an audit of ERC-8004's on-chain
41
+ reputation registry found the large majority of reviewers exhibited
42
+ coordinated Sybil behaviour, and after removing them most rated agents
43
+ had no valid feedback left.
44
+
45
+ Credit scoring solved this problem a long time ago, and not with better
46
+ surveys. It solved it structurally: **the party being scored is never
47
+ the party reporting.** Your bank reports your payments. You cannot file
48
+ your own. You cannot edit the file. You cannot see the number until
49
+ someone pulls it.
50
+
51
+ Kredisco applies that structure to agents.
52
+
53
+ ## What the score is made of
54
+
55
+ Like FICO, the score is a weighted blend of factors, and like FICO,
56
+ every one of them comes from somewhere the scored party cannot reach.
57
+
58
+ | Factor | Weight | Where it comes from |
59
+ |---|---:|---|
60
+ | Rework rate (inverted) | 0.45 | You retried, reassigned, or the next step errored |
61
+ | On-time rate | 0.20 | Measured by the SDK wrapping the call |
62
+ | Counterparty diversity | 0.20 | How many distinct organizations hired it |
63
+ | History depth | 0.15 | Volume of recorded work |
64
+
65
+ Scores run 300–850. A new agent scores 300, so abandoning a damaged
66
+ identity means abandoning everything it earned.
67
+
68
+ Rework carries the most weight because it is hardest to fake. An agent
69
+ cannot retry itself, cannot stop a downstream step from failing, and
70
+ cannot stop you from routing around it.
71
+
72
+ ## Who reports, and why it can't be gamed
73
+
74
+ A lender reports your payment to the bureau. The bureau holds the file.
75
+ A future lender queries the bureau, never you.
76
+
77
+ Kredisco works the same way. Your orchestrator is the reporter, Kredisco
78
+ is the bureau, and the agent is the subject:
79
+
80
+ 1. Your agent completes a task
81
+ 2. The agent signs a record of it; your orchestrator countersigns
82
+ 3. Your orchestrator submits it, authenticated with your API key
83
+ 4. Kredisco verifies both signatures and files it under your organization
84
+ 5. The score is recomputed from everything on file
85
+
86
+ Both signatures are required, so neither side can act alone. An agent
87
+ cannot manufacture a history, because every entry needs a real
88
+ counterparty to have signed it. And it never holds its own score — when
89
+ someone wants to know whether to trust it, they ask Kredisco.
90
+
91
+ ---
92
+
93
+ ## Quickstart
94
+
95
+ ### 1. Get a key
96
+
97
+ Sign in with GitHub at the Kredisco dashboard and create an API key. One
98
+ key covers everything you build.
99
+
100
+ ```bash
101
+ export KREDISCO_API_KEY=kd_...
102
+ ```
103
+
104
+ ### 2. Install
105
+
106
+ ```bash
107
+ pip install kredisco
108
+ ```
109
+
110
+ ### 3. Connect
111
+
112
+ ```python
113
+ import os
114
+ from kredisco import Kredisco
115
+
116
+ kd = Kredisco(api_key=os.environ["KREDISCO_API_KEY"])
117
+ ```
118
+
119
+ ### 4. Name each agent you want scored
120
+
121
+ An "agent" is any component you call and could imagine replacing: a
122
+ model behind a prompt, a third-party API, a tool, a subprocess. Give it
123
+ a name once. Kredisco creates its identity and reuses it forever after.
124
+
125
+ ```python
126
+ extractor = kd.agent("invoice-extractor")
127
+ reviewer = kd.agent("code-reviewer")
128
+ ```
129
+
130
+ ### 5. Route your calls through `track`
131
+
132
+ Take the call you already make:
133
+
134
+ ```python
135
+ data = extract_invoice(pdf)
136
+ ```
137
+
138
+ Hand the function to `track` instead of calling it yourself:
139
+
140
+ ```python
141
+ data = kd.track(extractor, "extract", extract_invoice, pdf)
142
+ ```
143
+
144
+ Four arguments: the agent, a label for the kind of work, **the function
145
+ without parentheses**, then its arguments.
146
+
147
+ Kredisco calls the function, times it, files a record, and hands back
148
+ exactly what your function returned. If it raises, the failure is
149
+ recorded and the exception propagates normally. Your logic does not
150
+ change.
151
+
152
+ That is the integration. Scores start appearing on the dashboard.
153
+
154
+ ---
155
+
156
+ ## Telling Kredisco what counts as a failure
157
+
158
+ By default a task fails only if your function raises.
159
+
160
+ Most pipelines have a stricter rule — a required field, a schema, a
161
+ minimum length. Pass `validate` and Kredisco uses yours:
162
+
163
+ ```python
164
+ data = kd.track(
165
+ extractor, "extract", extract_invoice, pdf,
166
+ validate=lambda d: d is not None and "total" in d,
167
+ )
168
+ ```
169
+
170
+ **This matters more than it looks.** With no validator and nothing
171
+ raising, every task passes, and the largest factor in the score is
172
+ measuring nothing. Kredisco is worth most to pipelines that already
173
+ check their own output.
174
+
175
+ ## Retries
176
+
177
+ Add `retries=1` and a failed attempt runs again. Kredisco links the
178
+ retry to the original task, and that link is what drives the rework
179
+ factor:
180
+
181
+ ```python
182
+ data = kd.track(
183
+ extractor, "extract", extract_invoice, pdf,
184
+ validate=lambda d: d is not None and "total" in d,
185
+ retries=1,
186
+ )
187
+ ```
188
+
189
+ If your orchestrator already retries on its own, leave this off and use
190
+ the context manager below for each attempt.
191
+
192
+ ## When everything fails
193
+
194
+ If every attempt fails, `track` re-raises the last exception. Sometimes
195
+ that is what you want. Often it is not — a graph node that stops the
196
+ whole run because one step returned bad JSON is worse than a node that
197
+ carries on with an empty value.
198
+
199
+ Pass `default` and `track` returns it instead of raising:
200
+
201
+ ```python
202
+ data = kd.track(
203
+ extractor, "extract", extract_invoice, pdf,
204
+ validate=lambda d: d is not None and "total" in d,
205
+ retries=1,
206
+ default={},
207
+ )
208
+ ```
209
+
210
+ The failure is still recorded and the score still drops. Only your
211
+ control flow changes.
212
+
213
+ Without `default`, every call site needs its own `try`/`except`. With
214
+ it, a pipeline step is one expression:
215
+
216
+ ```python
217
+ def extract_node(state):
218
+ return {"fields": kd.track(
219
+ extractor, "extract", extract_invoice, state["pdf"],
220
+ validate=fields_ok, retries=1, default={},
221
+ )}
222
+ ```
223
+
224
+ ## When you cannot hand over a function
225
+
226
+ Some code will not collapse into a single call — the body of a graph
227
+ node, a streamed response consumed in pieces, a block with setup and
228
+ teardown around it. Open a task and report the outcome yourself:
229
+
230
+ ```python
231
+ with kd.task(reviewer, "review") as t:
232
+ findings = run_review(diff)
233
+ t.accepted = len(findings) > 0
234
+ ```
235
+
236
+ The clock starts when the block opens and stops when it closes. An
237
+ exception inside records a failure and re-raises.
238
+
239
+ ## Grouping by pipeline
240
+
241
+ If you run more than one pipeline, tag each one. Same key, different
242
+ label — they appear as separate groups on the dashboard but roll up to
243
+ one organization.
244
+
245
+ ```python
246
+ kd = Kredisco(
247
+ api_key=os.environ["KREDISCO_API_KEY"],
248
+ workflow_id="invoice-ingest",
249
+ )
250
+ ```
251
+
252
+ ## Reading scores
253
+
254
+ ```python
255
+ kd.score(extractor.pubkey) # one number, 300–850
256
+ kd.breakdown(extractor.pubkey) # what the score is made of
257
+ kd.dashboard() # your agents, grouped by pipeline
258
+ kd.best("extract", minimum=650) # highest scorer for a kind of work
259
+ ```
260
+
261
+ `best` is the point of the whole thing: pick who does the work by track
262
+ record instead of hardcoding a vendor and hoping.
263
+
264
+ ## Things worth knowing
265
+
266
+ **Reporting never breaks your pipeline.** If Kredisco is unreachable or
267
+ your key is rejected, the failure is logged and your work continues.
268
+
269
+ ```python
270
+ import logging
271
+ logging.getLogger("kredisco").setLevel(logging.INFO)
272
+ ```
273
+
274
+ **Agent keys live on disk.** Kredisco writes each agent's keypair to
275
+ `.kredisco/`. That directory *is* your agents' identity — lose it and
276
+ every score resets to 300. Back it up, and add it to `.gitignore`.
277
+
278
+ **Timestamps come from the SDK**, not from your agent, so an agent
279
+ cannot shorten its own recorded duration.
280
+
281
+ ---
282
+
283
+ ---
284
+
285
+ ## A working example
286
+
287
+ [`examples/langgraph_support_triage.py`](examples/langgraph_support_triage.py)
288
+ is a four-agent LangGraph pipeline built on two different Claude models,
289
+ with a validator on every step.
290
+
291
+ Run it a few times. The scores separate for real reasons — one agent
292
+ returns JSON wrapped in code fences, another hits a response shape the
293
+ caller did not expect, a third is simply slower. Fix the prompt that is
294
+ failing and watch that agent climb over subsequent runs, without ever
295
+ catching the agent that never failed.
296
+
297
+ That gap is the point. History has weight, and recovery takes work.
298
+
299
+ ---
300
+
301
+ ## What this does not solve
302
+
303
+ **A dishonest reporter can fabricate everything.** Signatures prove who
304
+ signed, not that the content is true. Credit bureaus have the same hole
305
+ and handle it with licensing and liability rather than cryptography.
306
+ Kredisco's mitigation is that keys are tied to GitHub accounts and the
307
+ diversity factor requires many distinct organizations.
308
+
309
+ **Identity is still cheap.** A bad score can be abandoned for a fresh
310
+ keypair. Starting at the floor makes that costly, not impossible.
311
+
312
+ **One score across all task types.** An agent good at summarising and
313
+ bad at code review averages into noise.
314
+
315
+ **Rate limiting is per-process** and does not survive horizontal
316
+ scaling.
317
+
318
+ **No test suite.** The scoring model has changed several times and was
319
+ verified by inspection.
320
+
321
+ ---
322
+
323
+ [![LinkedIn](https://img.shields.io/badge/LinkedIn-0A66C2?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/in/reachrshah)
324
+ [![GitHub](https://img.shields.io/badge/GitHub-14161A?style=for-the-badge&logo=github&logoColor=white)](https://github.com/idfwyy)
@@ -0,0 +1,11 @@
1
+ README.md
2
+ pyproject.toml
3
+ kredisco/__init__.py
4
+ kredisco/client.py
5
+ kredisco/identity.py
6
+ kredisco/receipt.py
7
+ kredisco.egg-info/PKG-INFO
8
+ kredisco.egg-info/SOURCES.txt
9
+ kredisco.egg-info/dependency_links.txt
10
+ kredisco.egg-info/requires.txt
11
+ kredisco.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ requests>=2.28
2
+ cryptography>=41.0
@@ -0,0 +1 @@
1
+ kredisco
@@ -0,0 +1,24 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "kredisco"
7
+ version = "0.1.0"
8
+ description = "A trust layer for AI agents"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ authors = [{name = "Rahul Sah"}]
13
+ keywords = ["ai", "agents", "trust", "reputation", "llm", "langgraph"]
14
+ dependencies = [
15
+ "requests>=2.28",
16
+ "cryptography>=41.0",
17
+ ]
18
+
19
+ [project.urls]
20
+ Homepage = "https://kredisco.com"
21
+ Source = "https://github.com/kredisco/Kredisco"
22
+
23
+ [tool.setuptools]
24
+ packages = ["kredisco"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+