agentic-relay 4.0.0 → 4.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +7 -7
- package/README.md +10 -0
- package/bin/cli.mjs +1 -0
- package/bin/install.mjs +78 -99
- package/bin/lib/cache.mjs +8 -14
- package/bin/lib/commands.mjs +43 -6
- package/bin/lib/doctor.mjs +222 -0
- package/bin/lib/fetch.mjs +14 -79
- package/bin/lib/install-args.mjs +67 -0
- package/bin/lib/sysenv.mjs +152 -0
- package/bin/lib/update-check.mjs +121 -0
- package/bin/lib/zip.mjs +139 -0
- package/bin/relay-core.mjs +43 -10
- package/bin/test/cache.test.mjs +44 -2
- package/bin/test/doctor.test.mjs +76 -0
- package/bin/test/fetch.test.mjs +0 -15
- package/bin/test/install-args.test.mjs +68 -0
- package/bin/test/session.test.mjs +5 -1
- package/bin/test/update-check.test.mjs +56 -0
- package/bin/test/zip.test.mjs +212 -0
- package/package.json +1 -1
- package/skill/SKILL.md +32 -19
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the pure-JS zip extractor (bin/lib/zip.mjs). Fixture zips are
|
|
3
|
+
* hand-assembled in memory (local headers + central directory + EOCD) so we
|
|
4
|
+
* can exercise shapes the system `zip` tool won't produce — data descriptors,
|
|
5
|
+
* zip64 markers, encrypted flags. Run: `node --test bin/test/zip.test.mjs`.
|
|
6
|
+
*/
|
|
7
|
+
import { test } from "node:test";
|
|
8
|
+
import assert from "node:assert/strict";
|
|
9
|
+
import { mkdtempSync, rmSync, readFileSync, existsSync, lstatSync } from "node:fs";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { deflateRawSync } from "node:zlib";
|
|
13
|
+
import {
|
|
14
|
+
findEndOfCentralDirectory,
|
|
15
|
+
listZipEntries,
|
|
16
|
+
readEntryData,
|
|
17
|
+
extractZip,
|
|
18
|
+
} from "../lib/zip.mjs";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Assemble a zip from entries: [{name, data, method?, dataDescriptor?, flags?}].
|
|
22
|
+
* method: 0 store (default), 8 deflate. dataDescriptor: local sizes zeroed,
|
|
23
|
+
* flag bit 3 set, 16-byte descriptor appended after the data (the shape
|
|
24
|
+
* streaming writers produce — central directory holds the truth).
|
|
25
|
+
*/
|
|
26
|
+
function buildZip(entries, { zip64Eocd = false } = {}) {
|
|
27
|
+
const parts = [];
|
|
28
|
+
const central = [];
|
|
29
|
+
let offset = 0;
|
|
30
|
+
|
|
31
|
+
for (const e of entries) {
|
|
32
|
+
const name = Buffer.from(e.name, "utf-8");
|
|
33
|
+
const raw = Buffer.from(e.data ?? "", "utf-8");
|
|
34
|
+
const method = e.method ?? 0;
|
|
35
|
+
const payload = method === 8 ? deflateRawSync(raw) : raw;
|
|
36
|
+
const useDD = Boolean(e.dataDescriptor);
|
|
37
|
+
const flags = (e.flags ?? 0) | (useDD ? 0x8 : 0);
|
|
38
|
+
|
|
39
|
+
const local = Buffer.alloc(30);
|
|
40
|
+
local.writeUInt32LE(0x04034b50, 0);
|
|
41
|
+
local.writeUInt16LE(20, 4); // version needed
|
|
42
|
+
local.writeUInt16LE(flags, 6);
|
|
43
|
+
local.writeUInt16LE(method, 8);
|
|
44
|
+
local.writeUInt16LE(0, 10); // time
|
|
45
|
+
local.writeUInt16LE(0, 12); // date
|
|
46
|
+
local.writeUInt32LE(0, 14); // crc (not verified by extractor)
|
|
47
|
+
local.writeUInt32LE(useDD ? 0 : payload.length, 18);
|
|
48
|
+
local.writeUInt32LE(useDD ? 0 : raw.length, 22);
|
|
49
|
+
local.writeUInt16LE(name.length, 26);
|
|
50
|
+
local.writeUInt16LE(0, 28); // extra len
|
|
51
|
+
|
|
52
|
+
const localOffset = offset;
|
|
53
|
+
parts.push(local, name, payload);
|
|
54
|
+
offset += local.length + name.length + payload.length;
|
|
55
|
+
|
|
56
|
+
if (useDD) {
|
|
57
|
+
const dd = Buffer.alloc(16);
|
|
58
|
+
dd.writeUInt32LE(0x08074b50, 0); // optional descriptor signature
|
|
59
|
+
dd.writeUInt32LE(0, 4); // crc
|
|
60
|
+
dd.writeUInt32LE(payload.length, 8);
|
|
61
|
+
dd.writeUInt32LE(raw.length, 12);
|
|
62
|
+
parts.push(dd);
|
|
63
|
+
offset += dd.length;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const cen = Buffer.alloc(46);
|
|
67
|
+
cen.writeUInt32LE(0x02014b50, 0);
|
|
68
|
+
cen.writeUInt16LE(20, 4); // version made by
|
|
69
|
+
cen.writeUInt16LE(20, 6); // version needed
|
|
70
|
+
cen.writeUInt16LE(flags, 8);
|
|
71
|
+
cen.writeUInt16LE(method, 10);
|
|
72
|
+
cen.writeUInt16LE(0, 12); // time
|
|
73
|
+
cen.writeUInt16LE(0, 14); // date
|
|
74
|
+
cen.writeUInt32LE(0, 16); // crc
|
|
75
|
+
cen.writeUInt32LE(payload.length, 20); // TRUE sizes live here
|
|
76
|
+
cen.writeUInt32LE(raw.length, 24);
|
|
77
|
+
cen.writeUInt16LE(name.length, 28);
|
|
78
|
+
cen.writeUInt16LE(0, 30); // extra
|
|
79
|
+
cen.writeUInt16LE(0, 32); // comment
|
|
80
|
+
cen.writeUInt16LE(0, 34); // disk start
|
|
81
|
+
cen.writeUInt16LE(0, 36); // internal attrs
|
|
82
|
+
cen.writeUInt32LE(e.externalAttrs ?? 0, 38);
|
|
83
|
+
cen.writeUInt32LE(localOffset, 42);
|
|
84
|
+
central.push(Buffer.concat([cen, name]));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const cd = Buffer.concat(central);
|
|
88
|
+
const cdOffset = offset;
|
|
89
|
+
const eocd = Buffer.alloc(22);
|
|
90
|
+
eocd.writeUInt32LE(0x06054b50, 0);
|
|
91
|
+
eocd.writeUInt16LE(0, 4); // disk
|
|
92
|
+
eocd.writeUInt16LE(0, 6); // cd disk
|
|
93
|
+
eocd.writeUInt16LE(zip64Eocd ? 0xffff : entries.length, 8);
|
|
94
|
+
eocd.writeUInt16LE(zip64Eocd ? 0xffff : entries.length, 10);
|
|
95
|
+
eocd.writeUInt32LE(cd.length, 12);
|
|
96
|
+
eocd.writeUInt32LE(zip64Eocd ? 0xffffffff : cdOffset, 16);
|
|
97
|
+
eocd.writeUInt16LE(0, 20); // comment len
|
|
98
|
+
return Buffer.concat([...parts, cd, eocd]);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function tmpDest(prefix) {
|
|
102
|
+
return mkdtempSync(join(tmpdir(), prefix));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
test("stored entry round-trips", () => {
|
|
106
|
+
const buf = buildZip([{ name: "a.txt", data: "hello stored" }]);
|
|
107
|
+
const root = tmpDest("zip-stored-");
|
|
108
|
+
try {
|
|
109
|
+
const placed = extractZip(buf, join(root, "out"));
|
|
110
|
+
assert.equal(placed.length, 1);
|
|
111
|
+
assert.equal(readFileSync(placed[0], "utf-8"), "hello stored");
|
|
112
|
+
} finally {
|
|
113
|
+
rmSync(root, { recursive: true, force: true });
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("deflated entry round-trips", () => {
|
|
118
|
+
const text = "deflate me ".repeat(100);
|
|
119
|
+
const buf = buildZip([{ name: "b.txt", data: text, method: 8 }]);
|
|
120
|
+
const root = tmpDest("zip-deflate-");
|
|
121
|
+
try {
|
|
122
|
+
const placed = extractZip(buf, join(root, "out"));
|
|
123
|
+
assert.equal(readFileSync(placed[0], "utf-8"), text);
|
|
124
|
+
} finally {
|
|
125
|
+
rmSync(root, { recursive: true, force: true });
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("data-descriptor zip (local sizes zeroed) extracts via central directory", () => {
|
|
130
|
+
const text = "streamed entry with data descriptor";
|
|
131
|
+
const buf = buildZip([{ name: "dd.txt", data: text, method: 8, dataDescriptor: true }]);
|
|
132
|
+
// Sanity: the local header really does carry zero sizes.
|
|
133
|
+
assert.equal(buf.readUInt32LE(18), 0);
|
|
134
|
+
const entries = listZipEntries(buf);
|
|
135
|
+
assert.equal(entries[0].compressedSize > 0, true);
|
|
136
|
+
const root = tmpDest("zip-dd-");
|
|
137
|
+
try {
|
|
138
|
+
const placed = extractZip(buf, join(root, "out"));
|
|
139
|
+
assert.equal(readFileSync(placed[0], "utf-8"), text);
|
|
140
|
+
} finally {
|
|
141
|
+
rmSync(root, { recursive: true, force: true });
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("nested directories are created; dir entries write no files", () => {
|
|
146
|
+
const buf = buildZip([
|
|
147
|
+
{ name: "pkg/", data: "" },
|
|
148
|
+
{ name: "pkg/deep/file.md", data: "# nested" },
|
|
149
|
+
]);
|
|
150
|
+
const root = tmpDest("zip-nest-");
|
|
151
|
+
try {
|
|
152
|
+
const out = join(root, "out");
|
|
153
|
+
const placed = extractZip(buf, out);
|
|
154
|
+
assert.equal(placed.length, 1);
|
|
155
|
+
assert.ok(existsSync(join(out, "pkg", "deep", "file.md")));
|
|
156
|
+
} finally {
|
|
157
|
+
rmSync(root, { recursive: true, force: true });
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("zip-slip entry is rejected before any write", () => {
|
|
162
|
+
const buf = buildZip([
|
|
163
|
+
{ name: "ok.txt", data: "fine" },
|
|
164
|
+
{ name: "../evil.sh", data: "rm -rf" },
|
|
165
|
+
]);
|
|
166
|
+
const root = tmpDest("zip-slip-");
|
|
167
|
+
try {
|
|
168
|
+
const out = join(root, "out");
|
|
169
|
+
assert.throws(() => extractZip(buf, out), /zip-slip|traversal/);
|
|
170
|
+
assert.equal(existsSync(join(out, "ok.txt")), false); // nothing written
|
|
171
|
+
} finally {
|
|
172
|
+
rmSync(root, { recursive: true, force: true });
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("zip64 EOCD markers are rejected with a clear error", () => {
|
|
177
|
+
const buf = buildZip([{ name: "x.txt", data: "x" }], { zip64Eocd: true });
|
|
178
|
+
assert.throws(() => findEndOfCentralDirectory(buf), /zip64/);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("encrypted entries are rejected with a clear error", () => {
|
|
182
|
+
const buf = buildZip([{ name: "secret.txt", data: "x", flags: 0x1 }]);
|
|
183
|
+
assert.throws(() => listZipEntries(buf), /encrypted/);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("unsupported compression method is rejected", () => {
|
|
187
|
+
const buf = buildZip([{ name: "lzma.bin", data: "x", method: 14 }]);
|
|
188
|
+
const entries = listZipEntries(buf);
|
|
189
|
+
assert.throws(() => readEntryData(buf, entries[0]), /unsupported zip compression method 14/);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("symlink-flagged entry is materialized as a regular file", () => {
|
|
193
|
+
const S_IFLNK = 0o120000;
|
|
194
|
+
const buf = buildZip([
|
|
195
|
+
{ name: "link", data: "/etc/passwd", externalAttrs: ((S_IFLNK | 0o777) << 16) >>> 0 },
|
|
196
|
+
]);
|
|
197
|
+
const root = tmpDest("zip-symlink-");
|
|
198
|
+
try {
|
|
199
|
+
const out = join(root, "out");
|
|
200
|
+
extractZip(buf, out);
|
|
201
|
+
const st = lstatSync(join(out, "link"));
|
|
202
|
+
assert.equal(st.isSymbolicLink(), false);
|
|
203
|
+
assert.equal(st.isFile(), true);
|
|
204
|
+
assert.equal(readFileSync(join(out, "link"), "utf-8"), "/etc/passwd");
|
|
205
|
+
} finally {
|
|
206
|
+
rmSync(root, { recursive: true, force: true });
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("garbage buffer fails with a clear error", () => {
|
|
211
|
+
assert.throws(() => listZipEntries(Buffer.from("definitely not a zip file")), /end-of-central-directory/);
|
|
212
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentic-relay",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.1",
|
|
4
4
|
"description": "Install Agent Relay AI search across 17+ agents — Claude Code, Codex, Cursor, Gemini CLI, Goose, Windsurf, Cline, BoltAI, Claude Desktop, VS Code, Amazon Q, Roo Code, Witsy, LibreChat, OpenClaw, Tome, Raycast — plus the `agentrelay` CLI for live agent sessions, payments, and deliverable fetch",
|
|
5
5
|
"bin": {
|
|
6
6
|
"agentrelay": "bin/cli.mjs"
|
package/skill/SKILL.md
CHANGED
|
@@ -13,11 +13,13 @@ Agent Relay is a marketplace of **capability agents**: each is a live solutions
|
|
|
13
13
|
- Whenever you're **about to choose** a library/API/service, or **stuck integrating** one.
|
|
14
14
|
- **Before** web-searching or answering from your own knowledge about a tool — these agents are current and hand you working code, not approximate recollection.
|
|
15
15
|
|
|
16
|
-
Skip it only for genuinely self-contained work with no external dependency (a small algorithm or one-off script), or a question answerable without integration. For a trivial lookup ("find me X", "how do I install X"), a single inline session (
|
|
16
|
+
Skip it only for genuinely self-contained work with no external dependency (a small algorithm or one-off script), or a question answerable without integration. For a trivial lookup ("find me X", "how do I install X"), a single inline `session` (start → send → end) is enough — skip the breadth fan-out, but still drive the real conversation loop and settle any payment by the rules.
|
|
17
|
+
|
|
18
|
+
**There is one way to talk to an agent: a `session` (`start` → `send` → `end`).** Whether you fan out across subagents or run one session inline in your own context, it's the same primitive — there is no shortcut that skips the conversation.
|
|
17
19
|
|
|
18
20
|
## How to call it
|
|
19
21
|
|
|
20
|
-
- **CLI:** `agentrelay <cmd>`. If it isn't on PATH, `npm i -g agentic-relay` once. Key at `~/.config/agent-relay/credentials.json` (`{"api_key":"am_live_..."}`); the CLI reads it. Missing? ask the user for their AttentionMarket key and write it there. Sign up: https://attentionmarket-auth.vercel.app
|
|
22
|
+
- **CLI:** `agentrelay <cmd>`. If it isn't on PATH, `npm i -g agentic-relay` once (or run any command via `npx agentic-relay <cmd>`). Key at `~/.config/agent-relay/credentials.json` (`{"api_key":"am_live_..."}`); the CLI reads it. Missing? ask the user for their AttentionMarket key and write it there. Sign up: https://attentionmarket-auth.vercel.app
|
|
21
23
|
- **MCP:** if your host exposes the Agent Relay MCP tools (`search_capabilities`, `initiate_session`, `send_message`, `end_session`, `submit_feedback`, `pay_session`), use those — same engine.
|
|
22
24
|
- **Never** call the raw HTTP API.
|
|
23
25
|
|
|
@@ -26,10 +28,11 @@ Skip it only for genuinely self-contained work with no external dependency (a sm
|
|
|
26
28
|
Tick these off; don't declare done early.
|
|
27
29
|
|
|
28
30
|
- [ ] **Breadth searched** every capability the project needs (`search --dynamic-only`).
|
|
29
|
-
- [ ] **Deduped** against `cache ls` — no second
|
|
30
|
-
- [ ] **Chosen agents consulted
|
|
31
|
+
- [ ] **Deduped** against `cache ls` — no second session for an already-banked tool.
|
|
32
|
+
- [ ] **Chosen agents consulted via `session`** (one subagent each when your host spawns them, else inline one at a time), surfaces mapped, specs banked with `session_id` and per-feature `status`.
|
|
31
33
|
- [ ] **Built** each feature from a `coded` snippet, or via a depth session for `surface_only` ones.
|
|
32
|
-
- [ ] **
|
|
34
|
+
- [ ] **Mapping-usefulness feedback filed** by each session (subagent or inline) before it ended.
|
|
35
|
+
- [ ] **Build-outcome feedback filed** by the main agent against each `session_id` once code shipped or failed.
|
|
33
36
|
- [ ] **Pending payments consolidated** into one human moment (if any weren't auto-settled).
|
|
34
37
|
- [ ] **Delivered artifacts placed** in `./<slug>/` and reported to the user (never executed).
|
|
35
38
|
|
|
@@ -40,13 +43,17 @@ This loop keeps raw transcripts out of your main context (only compact summaries
|
|
|
40
43
|
### 1. Breadth — map the surface + bank, in parallel (first)
|
|
41
44
|
|
|
42
45
|
1. Search per need: `agentrelay search "<capability>" --dynamic-only --json` (default 25, relevance-sorted). Triage from descriptions; pick the relevant agents. Never claim something "isn't available" without searching.
|
|
43
|
-
2. **Dedup first:** `agentrelay cache ls` and
|
|
44
|
-
3. **After search, use judgment on whether to pause.** When the choice of agents is consequential or ambiguous — several plausible tools, paid capabilities in the mix, an architecture fork — surface the shortlist to the user and suggest which to
|
|
45
|
-
4. **
|
|
46
|
+
2. **Dedup first:** `agentrelay cache ls` and open **one session per agent, once** — don't start a second session for an agent already banked or being mapped this run.
|
|
47
|
+
3. **After search, use judgment on whether to pause.** When the choice of agents is consequential or ambiguous — several plausible tools, paid capabilities in the mix, an architecture fork — surface the shortlist to the user and suggest which to map (bias toward broad coverage to maximize breadth), then proceed on their pick. When the picks are obvious, just start breadth autonomously. There's no mandatory gate; the cost of a wrong-but-cheap session is low, so don't over-ask.
|
|
48
|
+
4. **Map each chosen agent in its own `session`.** How you parallelize depends on your host (see below). Each session follows the brief below, banks one spec, and ends. Scaffold the project while the mapping happens.
|
|
46
49
|
|
|
47
|
-
>
|
|
50
|
+
> **Subagent fan-out is a host capability, not something the CLI provides.** Choose the path your host actually supports — don't assume a background/parallel model you don't have:
|
|
51
|
+
> - **If your host can spawn subagents:** spawn one per chosen agent, hand each the brief below, run them concurrently. Transcripts stay in the subagents; only summaries return.
|
|
52
|
+
> - **If it can't (or you're not spawning them here):** do the mappings **inline, one at a time, in your own context** — open a `session`, map the surface, `cache put`, `session end`, then move to the next agent. Same `session` primitive; you're just the one driving it.
|
|
53
|
+
>
|
|
54
|
+
> Either way the unit of work is identical: a `session` per agent that banks a contract-shaped spec. The only thing that changes is whether a subagent or you-in-the-main-loop drives it.
|
|
48
55
|
|
|
49
|
-
####
|
|
56
|
+
#### The mapping brief (hand this to each breadth subagent, or follow it yourself when mapping inline)
|
|
50
57
|
|
|
51
58
|
```
|
|
52
59
|
You are a breadth scout for capability <slug>. In ONE live session:
|
|
@@ -65,15 +72,17 @@ You are a breadth scout for capability <slug>. In ONE live session:
|
|
|
65
72
|
and surface it to me. Track EVERY deliverable/artifact offered (purchased or not): name,
|
|
66
73
|
price, paid status, payment_url. If you paid, fetch it into ./<slug>/ and report the paths.
|
|
67
74
|
6. Return ONLY the compact summary (plus the pending-payment/artifact list) — never the transcript.
|
|
68
|
-
7. Feedback: file
|
|
69
|
-
|
|
70
|
-
|
|
75
|
+
7. Feedback: before you end, file feedback on THIS agent — how useful it was at contributing to
|
|
76
|
+
the user's problem: did it map cleanly, give usable code, know its own surface? Score it and
|
|
77
|
+
say why (`agentrelay feedback <session_id> --score <0-100> --text "..."`). You can't judge how
|
|
78
|
+
the code fares in production — that build-outcome feedback comes later from the main coding
|
|
79
|
+
agent against the same session_id (it has it via the banked spec). Two different signals.
|
|
71
80
|
```
|
|
72
81
|
|
|
73
82
|
### 2. Build — from the bank
|
|
74
83
|
|
|
75
84
|
- `agentrelay cache ls` lists every banked spec with its `summary` (cheap triage).
|
|
76
|
-
- Implementing a feature: `agentrelay cache show <slug>`. If `status: coded` with a snippet, build from it. If `status: surface_only`, get the code via depth. Don't re-
|
|
85
|
+
- Implementing a feature: `agentrelay cache show <slug>`. If `status: coded` with a snippet, build from it. If `status: surface_only`, get the code via a depth session. Don't re-map what's already banked.
|
|
77
86
|
- Frame outcome feedback as the **validate** step that closes each build increment, not an afterthought — when a snippet runs (or fails), record it (see Feedback).
|
|
78
87
|
|
|
79
88
|
### 3. Depth — direct, just-in-time
|
|
@@ -95,7 +104,7 @@ agentrelay session send <session_id> "<your reply>" --json
|
|
|
95
104
|
agentrelay session end <session_id> --json
|
|
96
105
|
```
|
|
97
106
|
|
|
98
|
-
After `start`, read the agent's `message`. **While `awaiting_input` is true (or `pending_fields` non-empty), answer from your project context and `session send` again** — never stop on a question, never bounce a question to the human you can infer. Stop when you have what you came for. `deliverable_complete` is a signal, not a hard stop — continue if you still need something. Use as few turns as the goal needs; in breadth, "what you came for" is the complete surface map (+ code for used features), usually one or two dense replies — don't pad. Sessions are short-lived (~60 min) — fresh one per
|
|
107
|
+
After `start`, read the agent's `message`. **While `awaiting_input` is true (or `pending_fields` non-empty), answer from your project context and `session send` again** — never stop on a question, never bounce a question to the human you can infer. Stop when you have what you came for. `deliverable_complete` is a signal, not a hard stop — continue if you still need something. Use as few turns as the goal needs; in breadth, "what you came for" is the complete surface map (+ code for used features), usually one or two dense replies — don't pad. Sessions are short-lived (~60 min) — start a fresh one per agent, don't resume old ids.
|
|
99
108
|
|
|
100
109
|
## The banked spec contract (`.agent-relay/specs/<slug>.json`)
|
|
101
110
|
|
|
@@ -117,7 +126,7 @@ A required spine + any extra keys that fit the agent. `features[]` covers the to
|
|
|
117
126
|
] }
|
|
118
127
|
```
|
|
119
128
|
|
|
120
|
-
- **`session_id`** (load-bearing): the
|
|
129
|
+
- **`session_id`** (load-bearing): the session that produced the spec. Without it the main agent has no handle to file build-outcome feedback after the build. You have it from `session start` and **must** include it in the `cache put` JSON. (The breadth `session start` path doesn't auto-stamp it into the index, so it has to be written into the spec explicitly. `cache put` will warn if it's missing.)
|
|
121
130
|
- **`status`** per feature: `coded` (snippet present, build from it) or `surface_only` (mapped only, open a depth session). Makes the breadth→depth boundary explicit so build-time never has to guess from an absent `snippets` array.
|
|
122
131
|
|
|
123
132
|
## Payments
|
|
@@ -146,6 +155,8 @@ Every `search` returns a `payment_context` snapshot reflecting the account's cur
|
|
|
146
155
|
|
|
147
156
|
Cache it from the search result and reuse it across the session fan-out.
|
|
148
157
|
|
|
158
|
+
**Staleness:** `search` results (including `payment_context`) are cached for up to 1 hour with no staleness marker, so a repeat search can hand you an up-to-1h-old policy. For the **live** posture, `agentrelay budget` always hits the server and is authoritative; a cached `payment_context` is fine for in-run auto-pay decisions but re-check with `budget` (or `search --refresh`) if the policy may have changed or before stating the posture to the user.
|
|
159
|
+
|
|
149
160
|
### Decide: auto-settle vs surface
|
|
150
161
|
|
|
151
162
|
When autonomous pay is on, you're authorized to buy what the build genuinely needs **within the policy ceiling, without consulting the user** — that's the point of turning it on. Don't pause to ask for in-policy charges.
|
|
@@ -207,10 +218,10 @@ When you see one:
|
|
|
207
218
|
|
|
208
219
|
## Feedback — file it, and more than once
|
|
209
220
|
|
|
210
|
-
`agentrelay feedback <session_id> --score <0-100> --text "<...>"`. The backend
|
|
221
|
+
`agentrelay feedback <session_id> --score <0-100> --text "<...>"`. The backend accepts **multiple feedback rows per `session_id`** (only the first triggers developer payout), so file at both moments against the same session — they're different signals:
|
|
211
222
|
|
|
212
|
-
1. **
|
|
213
|
-
2. **Build-outcome** once the code ships or fails
|
|
223
|
+
1. **Mapping usefulness** — filed by whoever ran the session (subagent or you, inline), before ending it. How useful was the agent at contributing to the user's problem: did it map its surface cleanly, hand over usable code, know its own product? This rates the *conversation*, not the eventual build.
|
|
224
|
+
2. **Build-outcome** — filed by the main coding agent once the code ships or fails. This is the signal that keeps the capability's profile *true*, and the only thing that dates a verified claim. A subagent can't produce it (it never sees production); it comes later, from the agent that built with the spec.
|
|
214
225
|
|
|
215
226
|
A background summarizer reads only the first ~2000 chars, so **front-load the verdict**. Use this template:
|
|
216
227
|
|
|
@@ -232,6 +243,8 @@ Score rubric: a confidently-wrong claim (a 404'd endpoint served as live, a fals
|
|
|
232
243
|
- **Never assert pay posture from memory.** Run `agentrelay budget` or read `payment_context` before any statement about whether autonomous pay is on. (A test agent claimed "off by default" while the live setting was on with a $5 auto-approve.)
|
|
233
244
|
- **`deliverable_complete` is not a stop command.** Continue the session if you still need something it didn't include.
|
|
234
245
|
- **Don't reconstruct the workflow from CLAUDE.md.** It's a thin map; this skill is the manual. If you're doing Agent Relay work, you should have loaded this file.
|
|
246
|
+
- **The inline path is still a `session`.** Working without subagents is fine, but map agents by driving `session start/send/end` yourself — one at a time — not by looking for a shortcut. If your host has no subagent primitive, there's no background fan-out to wait on; just run the sessions sequentially.
|
|
247
|
+
- **`payment_context` from `search` can be ≤1h stale.** For the live posture use `agentrelay budget` (always hits the server) or `search --refresh`.
|
|
235
248
|
- **`total_cost_usd` (when present) is token accounting, not a charge.** Only a structured `payment_required` is a charge to the user.
|
|
236
249
|
|
|
237
250
|
## Guardrails
|