@poa-box/agent 0.1.0 → 1.0.0
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/dist/commands/agent/daily-digest.js +47 -1
- package/dist/commands/agent/deploy-to-org.js +12 -15
- package/dist/commands/agent/init.d.ts +2 -1
- package/dist/commands/agent/init.js +4 -4
- package/dist/commands/agent/paymaster-status.d.ts +2 -1
- package/dist/commands/agent/paymaster-status.js +3 -3
- package/dist/commands/agent/setup-sponsorship.d.ts +2 -1
- package/dist/commands/agent/setup-sponsorship.js +3 -3
- package/dist/commands/agent/triage.js +59 -5
- package/docs/agents/onboarding-protocol.md +6 -0
- package/docs/agents/running-an-agent.md +24 -18
- package/package.json +14 -5
- package/scripts/apply.sh +9 -9
- package/scripts/onboard.sh +13 -7
- package/scripts/postpack-cli-link.mjs +11 -0
- package/scripts/prepack-cli-range.mjs +28 -0
|
@@ -107,6 +107,19 @@ const FETCH_DIGEST_DATA = `
|
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
`;
|
|
110
|
+
/**
|
|
111
|
+
* FETCH_DIGEST_DATA plus the TaskManager v7 claim-release fields (subgraph #201).
|
|
112
|
+
*
|
|
113
|
+
* Gnosis-only today — poa-arb-v-1 answers ``Type `Task` has no field `releaseCount```,
|
|
114
|
+
* which `isUnknownFieldError` matches, so this has to be a tier: agents run against both
|
|
115
|
+
* chains and a bare query would take the whole digest down on Arbitrum. Built by insertion
|
|
116
|
+
* next to the field it repairs so the two documents cannot drift apart.
|
|
117
|
+
*/
|
|
118
|
+
const FETCH_DIGEST_DATA_WITH_RELEASES = FETCH_DIGEST_DATA.replace(/^(\s*)assignedAt$/m, '$1assignedAt\n$1releaseCount\n$1lastReleasedAt');
|
|
119
|
+
const DIGEST_DATA_TIERS = [
|
|
120
|
+
FETCH_DIGEST_DATA_WITH_RELEASES, // 0: Gnosis — releases indexed
|
|
121
|
+
FETCH_DIGEST_DATA, // 1: Arbitrum today — no release indexing
|
|
122
|
+
];
|
|
110
123
|
function getGitCommits(sinceSec) {
|
|
111
124
|
try {
|
|
112
125
|
const sinceDate = new Date(Date.now() - sinceSec * 1000).toISOString();
|
|
@@ -151,7 +164,10 @@ exports.dailyDigestHandler = {
|
|
|
151
164
|
const sinceSec = parseSinceDuration(argv.since || '24h');
|
|
152
165
|
const sinceTs = Math.floor(Date.now() / 1000) - sinceSec;
|
|
153
166
|
const modules = await (0, resolve_1.resolveOrgModules)(argv.org, argv.chain);
|
|
154
|
-
const result = await (0, subgraph_1.
|
|
167
|
+
const { data: result, tierIndex } = await (0, subgraph_1.queryWithFieldFallback)(DIGEST_DATA_TIERS.map((q) => ({ query: q, variables: { orgId: modules.orgId } })), { chainId: argv.chain });
|
|
168
|
+
// Gate on the served tier, never on truthiness: releaseCount is 0 on every
|
|
169
|
+
// live row today, which must stay distinguishable from "not indexed here".
|
|
170
|
+
const hasReleaseData = tierIndex === 0;
|
|
155
171
|
const org = result.organization;
|
|
156
172
|
if (!org)
|
|
157
173
|
throw new Error('Organization not found');
|
|
@@ -164,6 +180,12 @@ exports.dailyDigestHandler = {
|
|
|
164
180
|
const tasksClaimed = allTasks.filter((t) => parseInt(t.assignedAt || '0') >= sinceTs && t.assignee);
|
|
165
181
|
const tasksSubmitted = allTasks.filter((t) => parseInt(t.submittedAt || '0') >= sinceTs);
|
|
166
182
|
const tasksCompleted = allTasks.filter((t) => parseInt(t.completedAt || '0') >= sinceTs);
|
|
183
|
+
// unclaimTask nulls assignee/assignedAt, so a claim-then-release inside the window
|
|
184
|
+
// leaves no trace in tasksClaimed above — the work would read as if it never happened.
|
|
185
|
+
// lastReleasedAt is the only surviving timestamp.
|
|
186
|
+
const tasksReleased = hasReleaseData
|
|
187
|
+
? allTasks.filter((t) => parseInt(t.lastReleasedAt || '0') >= sinceTs)
|
|
188
|
+
: [];
|
|
167
189
|
// PT earned in window
|
|
168
190
|
const ptEarnedInWindow = tasksCompleted.reduce((s, t) => s + parseFloat(ethers_1.ethers.utils.formatEther(t.payout || '0')), 0);
|
|
169
191
|
// Proposals — subgraph lacks createdAt on Proposal/Vote, so we show
|
|
@@ -246,6 +268,9 @@ exports.dailyDigestHandler = {
|
|
|
246
268
|
ptSupply: Math.round(ptSupply * 10) / 10,
|
|
247
269
|
totalVotesCast,
|
|
248
270
|
activeProposals: activeProposals.length,
|
|
271
|
+
// Appended at the end, gated on the served tier so a chain that does not
|
|
272
|
+
// index releases omits the key rather than reporting a false 0.
|
|
273
|
+
...(hasReleaseData ? { tasksReleased: tasksReleased.length } : {}),
|
|
249
274
|
},
|
|
250
275
|
activeProposals: activeProposals.map((p) => ({
|
|
251
276
|
id: p.proposalId,
|
|
@@ -264,6 +289,14 @@ exports.dailyDigestHandler = {
|
|
|
264
289
|
'Branch protection on main — requires repo admin (task #402)',
|
|
265
290
|
'Cross-org vouching (tasks #230, #277) — Hudson-gated',
|
|
266
291
|
],
|
|
292
|
+
...(hasReleaseData ? {
|
|
293
|
+
releasedTasks: tasksReleased.map((t) => ({
|
|
294
|
+
taskId: t.taskId,
|
|
295
|
+
title: t.title,
|
|
296
|
+
lastReleasedAt: t.lastReleasedAt ?? null,
|
|
297
|
+
releaseCount: Number(t.releaseCount ?? 0),
|
|
298
|
+
})),
|
|
299
|
+
} : {}),
|
|
267
300
|
};
|
|
268
301
|
if (output.isJsonMode()) {
|
|
269
302
|
output.json(digest);
|
|
@@ -280,6 +313,8 @@ exports.dailyDigestHandler = {
|
|
|
280
313
|
console.log(` PRs merged: ${prsMerged}`);
|
|
281
314
|
console.log(` Tasks created: ${tasksCreated.length}`);
|
|
282
315
|
console.log(` Tasks claimed: ${tasksClaimed.length}`);
|
|
316
|
+
if (hasReleaseData)
|
|
317
|
+
console.log(` Tasks released: ${tasksReleased.length}`);
|
|
283
318
|
console.log(` Tasks submitted: ${tasksSubmitted.length}`);
|
|
284
319
|
console.log(` Tasks completed: ${tasksCompleted.length} (${ptEarnedInWindow.toFixed(1)} PT earned)`);
|
|
285
320
|
console.log(` Total votes (all): ${totalVotesCast}`);
|
|
@@ -300,6 +335,17 @@ exports.dailyDigestHandler = {
|
|
|
300
335
|
console.log(` #${t.taskId} "${t.title}" by ${t.assigneeUsername || t.assignee?.slice(0, 10)}`);
|
|
301
336
|
}
|
|
302
337
|
}
|
|
338
|
+
// Named explicitly rather than folded into the counters: the row itself no
|
|
339
|
+
// longer says who let the task go, so the operator needs the task ids to
|
|
340
|
+
// chase it down (pop task view --task N shows the release history).
|
|
341
|
+
if (tasksReleased.length > 0) {
|
|
342
|
+
console.log('');
|
|
343
|
+
console.log(' Released Back to the Pool');
|
|
344
|
+
console.log(' ─────────────────────────');
|
|
345
|
+
for (const t of tasksReleased) {
|
|
346
|
+
console.log(` #${t.taskId} "${t.title}" (${t.releaseCount}x released, now ${t.status})`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
303
349
|
if (argv['per-agent']) {
|
|
304
350
|
console.log('');
|
|
305
351
|
console.log(' Per-Agent Breakdown');
|
|
@@ -37,9 +37,10 @@ exports.deployToOrgHandler = void 0;
|
|
|
37
37
|
const ethers_1 = require("ethers");
|
|
38
38
|
const signer_1 = require("@poa-box/cli/lib/signer");
|
|
39
39
|
const networks_1 = require("@poa-box/cli/config/networks");
|
|
40
|
+
const resolve_1 = require("@poa-box/cli/lib/resolve");
|
|
41
|
+
const authority_1 = require("@poa-box/cli/lib/authority");
|
|
40
42
|
const output = __importStar(require("@poa-box/cli/lib/output"));
|
|
41
43
|
const IDENTITY_REGISTRY = '0x8004A169FB4a3325136EB29fA0ceB6D2e539a432';
|
|
42
|
-
const EOA_DELEGATION = '0x776ec88A88E86e38d54a985983377f1A2A25ef8b';
|
|
43
44
|
exports.deployToOrgHandler = {
|
|
44
45
|
builder: (yargs) => yargs
|
|
45
46
|
.option('target-org', { type: 'string', demandOption: true, describe: 'Target org name' })
|
|
@@ -93,19 +94,15 @@ exports.deployToOrgHandler = {
|
|
|
93
94
|
});
|
|
94
95
|
// Step 4: Check if org exists on target chain
|
|
95
96
|
spin.text = 'Checking target org...';
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
orgMembers = org.users?.length || 0;
|
|
106
|
-
isMember = (org.users || []).some((u) => u.address?.toLowerCase() === signer.address.toLowerCase());
|
|
107
|
-
}
|
|
108
|
-
}
|
|
97
|
+
// Resolve only authority-ready organizations on the requested chain. Historical
|
|
98
|
+
// User rows include former members and are not proof of current membership.
|
|
99
|
+
const orgId = await (0, resolve_1.resolveOrgId)(argv.targetOrg, argv.chain);
|
|
100
|
+
const org = { users: [] };
|
|
101
|
+
await (0, authority_1.refreshAuthorityUsers)(org, orgId, argv.chain);
|
|
102
|
+
const members = org.users.filter(user => user.membershipStatus === 'Active');
|
|
103
|
+
const orgFound = true;
|
|
104
|
+
const orgMembers = members.length;
|
|
105
|
+
const isMember = members.some(user => user.address.toLowerCase() === signer.address.toLowerCase());
|
|
109
106
|
steps.push({
|
|
110
107
|
step: 'Target org',
|
|
111
108
|
status: orgFound ? (isMember ? 'MEMBER' : 'FOUND') : 'NOT_FOUND',
|
|
@@ -133,7 +130,7 @@ exports.deployToOrgHandler = {
|
|
|
133
130
|
else if (ready) {
|
|
134
131
|
console.log(' Ready to deploy. Next steps:');
|
|
135
132
|
console.log(` 1. Ask a ${argv.targetOrg} member to vouch for you`);
|
|
136
|
-
console.log(` 2. pop vouch claim --
|
|
133
|
+
console.log(` 2. pop vouch claim --subject <role-id> --org ${JSON.stringify(argv.targetOrg)} --chain ${argv.chain}`);
|
|
137
134
|
if (!hasIdentity)
|
|
138
135
|
console.log(` 3. pop agent register --name <name> --chain ${argv.chain}`);
|
|
139
136
|
if (!isDelegated)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Argv, ArgumentsCamelCase } from 'yargs';
|
|
2
2
|
interface InitArgs {
|
|
3
3
|
org: string;
|
|
4
|
+
subject?: string;
|
|
4
5
|
chain: number;
|
|
5
6
|
username?: string;
|
|
6
7
|
'hat-id'?: string;
|
|
@@ -10,7 +11,7 @@ export declare const initHandler: {
|
|
|
10
11
|
builder: (yargs: Argv) => Argv<{
|
|
11
12
|
username: string | undefined;
|
|
12
13
|
} & {
|
|
13
|
-
|
|
14
|
+
subject: string | undefined;
|
|
14
15
|
} & {
|
|
15
16
|
home: string | undefined;
|
|
16
17
|
}>;
|
|
@@ -47,13 +47,13 @@ const CHAIN_INFO = {
|
|
|
47
47
|
exports.initHandler = {
|
|
48
48
|
builder: (yargs) => yargs
|
|
49
49
|
.option('username', { type: 'string', describe: 'Agent username' })
|
|
50
|
-
.option('
|
|
50
|
+
.option('subject', { type: 'string', alias: 'hat-id', describe: 'Authority role or group subject ID for gas sponsorship' })
|
|
51
51
|
.option('home', { type: 'string', describe: 'Agent home directory (default: ~/.pop-agent)' }),
|
|
52
52
|
handler: async (argv) => {
|
|
53
53
|
const orgName = argv.org;
|
|
54
54
|
const chainId = argv.chain || 100;
|
|
55
55
|
const username = argv.username || '';
|
|
56
|
-
const hatId = argv.hatId || '';
|
|
56
|
+
const hatId = (argv.subject || argv.hatId) || '';
|
|
57
57
|
if (!orgName) {
|
|
58
58
|
output.error('--org is required. Specify the POP org name.');
|
|
59
59
|
process.exit(1);
|
|
@@ -83,7 +83,7 @@ exports.initHandler = {
|
|
|
83
83
|
`POP_DEFAULT_CHAIN=${chainId}`,
|
|
84
84
|
];
|
|
85
85
|
if (hatId)
|
|
86
|
-
envLines.push(`POP_HAT_ID=${hatId}`);
|
|
86
|
+
envLines.push(`POP_SUBJECT_ID=${hatId}`, `POP_HAT_ID=${hatId}`);
|
|
87
87
|
envLines.push('');
|
|
88
88
|
fs.writeFileSync(path.join(agentHome, '.env'), envLines.join('\n'));
|
|
89
89
|
// Write who-i-am.md
|
|
@@ -97,7 +97,7 @@ exports.initHandler = {
|
|
|
97
97
|
- **Org Name**: ${orgName}
|
|
98
98
|
- **Username**: ${username || '(register with pop user register --username <name>)'}
|
|
99
99
|
|
|
100
|
-
##
|
|
100
|
+
## Authority roles and groups
|
|
101
101
|
- (will be populated after vouching and joining)
|
|
102
102
|
|
|
103
103
|
## Operator
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import type { Argv, ArgumentsCamelCase } from 'yargs';
|
|
2
2
|
interface PaymasterStatusArgs {
|
|
3
3
|
org: string;
|
|
4
|
+
subject?: string;
|
|
4
5
|
'hat-id'?: string;
|
|
5
6
|
chain?: number;
|
|
6
7
|
rpc?: string;
|
|
7
8
|
}
|
|
8
9
|
export declare const paymasterStatusHandler: {
|
|
9
10
|
builder: (yargs: Argv) => Argv<{
|
|
10
|
-
|
|
11
|
+
subject: string | undefined;
|
|
11
12
|
}>;
|
|
12
13
|
handler: (argv: ArgumentsCamelCase<PaymasterStatusArgs>) => Promise<void>;
|
|
13
14
|
};
|
|
@@ -42,7 +42,7 @@ const output = __importStar(require("@poa-box/cli/lib/output"));
|
|
|
42
42
|
const resolve_1 = require("@poa-box/cli/lib/resolve");
|
|
43
43
|
exports.paymasterStatusHandler = {
|
|
44
44
|
builder: (yargs) => yargs
|
|
45
|
-
.option('
|
|
45
|
+
.option('subject', { type: 'string', alias: 'hat-id', describe: 'Authority role or group subject ID to check budget for (decimal or hex)' }),
|
|
46
46
|
handler: async (argv) => {
|
|
47
47
|
const spin = output.spinner('Checking paymaster status...');
|
|
48
48
|
spin.start();
|
|
@@ -51,7 +51,7 @@ exports.paymasterStatusHandler = {
|
|
|
51
51
|
const orgId = modules.orgId;
|
|
52
52
|
const rpcUrl = argv.rpc || 'https://rpc.gnosischain.com';
|
|
53
53
|
const client = (0, viem_1.createPublicClient)({ chain: chains_1.gnosis, transport: (0, viem_1.http)(rpcUrl) });
|
|
54
|
-
const pmAbi = require('
|
|
54
|
+
const pmAbi = require('@poa-box/cli/abi/PaymasterHub.json');
|
|
55
55
|
// Org config
|
|
56
56
|
const config = await client.readContract({
|
|
57
57
|
address: sponsored_1.PAYMASTER_HUB, abi: pmAbi,
|
|
@@ -75,7 +75,7 @@ exports.paymasterStatusHandler = {
|
|
|
75
75
|
args: [sponsored_1.PAYMASTER_HUB],
|
|
76
76
|
});
|
|
77
77
|
// Hat budget (if specified or from env)
|
|
78
|
-
const hatIdStr = argv.hatId || process.env.POP_HAT_ID;
|
|
78
|
+
const hatIdStr = argv.subject || argv.hatId || process.env.POP_SUBJECT_ID || process.env.POP_HAT_ID;
|
|
79
79
|
let budgetInfo = null;
|
|
80
80
|
if (hatIdStr) {
|
|
81
81
|
const hatId = BigInt(hatIdStr);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Argv, ArgumentsCamelCase } from 'yargs';
|
|
2
2
|
interface SetupSponsorshipArgs {
|
|
3
3
|
org: string;
|
|
4
|
+
subject?: string;
|
|
4
5
|
'hat-id': string;
|
|
5
6
|
'org-id': string;
|
|
6
7
|
'budget-per-day'?: number;
|
|
@@ -13,7 +14,7 @@ export declare const setupSponsorshipHandler: {
|
|
|
13
14
|
builder: (yargs: Argv) => Argv<{
|
|
14
15
|
"org-id": string;
|
|
15
16
|
} & {
|
|
16
|
-
|
|
17
|
+
subject: string;
|
|
17
18
|
} & {
|
|
18
19
|
"budget-per-day": number;
|
|
19
20
|
}>;
|
|
@@ -42,7 +42,7 @@ const output = __importStar(require("@poa-box/cli/lib/output"));
|
|
|
42
42
|
exports.setupSponsorshipHandler = {
|
|
43
43
|
builder: (yargs) => yargs
|
|
44
44
|
.option('org-id', { type: 'string', demandOption: true, describe: 'Org ID (bytes32 hex)' })
|
|
45
|
-
.option('
|
|
45
|
+
.option('subject', { type: 'string', alias: 'hat-id', demandOption: true, describe: 'Authority role or group subject ID (decimal or hex)' })
|
|
46
46
|
.option('budget-per-day', { type: 'number', default: 0.1, describe: 'Gas budget per day in xDAI' }),
|
|
47
47
|
handler: async (argv) => {
|
|
48
48
|
const spin = output.spinner('Setting up gas sponsorship...');
|
|
@@ -57,10 +57,10 @@ exports.setupSponsorshipHandler = {
|
|
|
57
57
|
const publicClient = (0, viem_1.createPublicClient)({ chain: chains_1.gnosis, transport: (0, viem_1.http)(rpcUrl) });
|
|
58
58
|
const walletClient = (0, viem_1.createWalletClient)({ account, chain: chains_1.gnosis, transport: (0, viem_1.http)(rpcUrl) });
|
|
59
59
|
const orgId = argv.orgId;
|
|
60
|
-
const hatId = BigInt(argv.hatId);
|
|
60
|
+
const hatId = BigInt((argv.subject || argv.hatId));
|
|
61
61
|
const subjectKey = (0, viem_1.pad)((0, viem_1.toHex)(hatId), { size: 32 });
|
|
62
62
|
const budgetWei = BigInt(Math.floor(argv.budgetPerDay * 1e18));
|
|
63
|
-
const pmAbi = require('
|
|
63
|
+
const pmAbi = require('@poa-box/cli/abi/PaymasterHub.json');
|
|
64
64
|
const results = {};
|
|
65
65
|
// Step 1: Check and set up EIP-7702 delegation
|
|
66
66
|
spin.text = 'Step 1/3: Checking EIP-7702 delegation...';
|
|
@@ -88,6 +88,18 @@ const FETCH_TRIAGE_DATA = `
|
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
`;
|
|
91
|
+
/**
|
|
92
|
+
* FETCH_TRIAGE_DATA plus the TaskManager v7 release counter (subgraph #201).
|
|
93
|
+
*
|
|
94
|
+
* Gnosis-only today — poa-arb-v-1 answers ``Type `Task` has no field `releaseCount```,
|
|
95
|
+
* which `isUnknownFieldError` matches, so this has to be a tier: agents run against both
|
|
96
|
+
* chains and a bare query would kill triage outright on Arbitrum.
|
|
97
|
+
*/
|
|
98
|
+
const FETCH_TRIAGE_DATA_WITH_RELEASES = FETCH_TRIAGE_DATA.replace(/^(\s*)rejectionCount$/m, '$1rejectionCount\n$1releaseCount');
|
|
99
|
+
const TRIAGE_DATA_TIERS = [
|
|
100
|
+
FETCH_TRIAGE_DATA_WITH_RELEASES, // 0: Gnosis — releases indexed
|
|
101
|
+
FETCH_TRIAGE_DATA, // 1: Arbitrum today — no release indexing
|
|
102
|
+
];
|
|
91
103
|
exports.triageHandler = {
|
|
92
104
|
builder: (yargs) => yargs,
|
|
93
105
|
handler: async (argv) => {
|
|
@@ -105,11 +117,14 @@ exports.triageHandler = {
|
|
|
105
117
|
const provider = new ethers_1.ethers.providers.JsonRpcProvider(networkConfig.resolvedRpc);
|
|
106
118
|
const [gasBalance, orgData] = await Promise.all([
|
|
107
119
|
provider.getBalance(wallet.address),
|
|
108
|
-
(0, subgraph_1.
|
|
120
|
+
(0, subgraph_1.queryWithFieldFallback)(TRIAGE_DATA_TIERS.map((q) => ({ query: q, variables: { orgId: modules.orgId } })), { chainId: argv.chain }),
|
|
109
121
|
]);
|
|
110
|
-
const org = orgData.organization;
|
|
122
|
+
const org = orgData.data.organization;
|
|
111
123
|
if (!org)
|
|
112
124
|
throw new Error('Organization not found');
|
|
125
|
+
// Gate on the served tier, never on truthiness: releaseCount is 0 on every
|
|
126
|
+
// live row today, which must stay distinguishable from "not indexed here".
|
|
127
|
+
const hasReleaseData = orgData.tierIndex === 0;
|
|
113
128
|
const now = Math.floor(Date.now() / 1000);
|
|
114
129
|
const actions = [];
|
|
115
130
|
const changes = [];
|
|
@@ -368,10 +383,48 @@ exports.triageHandler = {
|
|
|
368
383
|
for (const t of myAssigned) {
|
|
369
384
|
actions.push({ priority: 'MEDIUM', type: 'work', detail: `Task #${t.taskId} "${t.title}" assigned to you.`, data: { taskId: t.taskId } });
|
|
370
385
|
}
|
|
371
|
-
//
|
|
386
|
+
// Previously attempted work (TaskManager v7). `unclaimTask` nulls assignee and
|
|
387
|
+
// assignedAt, so a task that was rejected and then released — the documented exit
|
|
388
|
+
// route out of Submitted — comes back as a plain Open row that neither myRejected
|
|
389
|
+
// nor myAssigned can see. A non-zero counter is the only evidence left, so surface
|
|
390
|
+
// these separately: re-claiming work the agent just walked away from, or that
|
|
391
|
+
// someone else already bounced off, is the failure this prevents.
|
|
372
392
|
const openTasks = allTasks.filter((t) => t.status === 'Open');
|
|
373
|
-
|
|
374
|
-
|
|
393
|
+
const previouslyAttempted = openTasks.filter((t) => parseInt(t.rejectionCount || '0') > 0 ||
|
|
394
|
+
(hasReleaseData && parseInt(t.releaseCount || '0') > 0));
|
|
395
|
+
const attemptedIds = new Set(previouslyAttempted.map((t) => t.taskId));
|
|
396
|
+
const freshTasks = openTasks.filter((t) => !attemptedIds.has(t.taskId));
|
|
397
|
+
// PUSHED FIRST, and deliberately so. Both actions are MEDIUM and Array#sort is
|
|
398
|
+
// stable, so push order IS the order the agent reads them in — and the heartbeat
|
|
399
|
+
// works actions top-down. Emitting the warning after the offer let an agent claim
|
|
400
|
+
// the task before ever seeing it.
|
|
401
|
+
if (previouslyAttempted.length > 0) {
|
|
402
|
+
actions.push({
|
|
403
|
+
priority: 'MEDIUM',
|
|
404
|
+
type: 'previously-attempted',
|
|
405
|
+
detail: `${previouslyAttempted.length} open task(s) were already attempted (rejected and/or released) and are EXCLUDED from claim-task — run pop task view before re-claiming any of them.`,
|
|
406
|
+
data: {
|
|
407
|
+
tasks: previouslyAttempted.map((t) => ({
|
|
408
|
+
id: t.taskId,
|
|
409
|
+
title: t.title,
|
|
410
|
+
rejectionCount: parseInt(t.rejectionCount || '0'),
|
|
411
|
+
...(hasReleaseData ? { releaseCount: parseInt(t.releaseCount || '0') } : {}),
|
|
412
|
+
})),
|
|
413
|
+
},
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
// Open tasks available to claim — attempted ones removed, so acting on this
|
|
417
|
+
// action alone can never silently re-claim abandoned work. The counts stay
|
|
418
|
+
// honest about what was withheld rather than quietly shrinking the list.
|
|
419
|
+
if (freshTasks.length > 0) {
|
|
420
|
+
actions.push({
|
|
421
|
+
priority: 'MEDIUM',
|
|
422
|
+
type: 'claim-task',
|
|
423
|
+
detail: previouslyAttempted.length > 0
|
|
424
|
+
? `${freshTasks.length} open task(s) available to claim (${previouslyAttempted.length} previously-attempted excluded — see above).`
|
|
425
|
+
: `${freshTasks.length} open task(s) available to claim.`,
|
|
426
|
+
data: { tasks: freshTasks.map((t) => ({ id: t.taskId, title: t.title })) },
|
|
427
|
+
});
|
|
375
428
|
}
|
|
376
429
|
// --- 4. PLAN (LOW) ---
|
|
377
430
|
const hasWork = myAssigned.length > 0 || openTasks.length > 0 || pendingReviews.length > 0;
|
|
@@ -443,6 +496,7 @@ exports.triageHandler = {
|
|
|
443
496
|
openTasks: openTasks.length,
|
|
444
497
|
assignedTasks: myAssigned.length,
|
|
445
498
|
boardState: hasWork ? 'has-work' : 'empty',
|
|
499
|
+
previouslyAttempted: previouslyAttempted.length,
|
|
446
500
|
};
|
|
447
501
|
if (output.isJsonMode()) {
|
|
448
502
|
output.json({ actions, changes, context });
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
> Historical Argus onboarding notes (April 2026). Argus was retired in the
|
|
2
|
+
> Access v2 release. These old Hats commands are not instructions for current
|
|
3
|
+
> deployments. Use `pop org list` to choose an authority-ready organization and
|
|
4
|
+
> the [current membership guide](../../../../docs/guides/membership-roles-vouching.md)
|
|
5
|
+
> for `--subject` / `--user` vouching and explicit role claims.
|
|
6
|
+
|
|
1
7
|
# Agent Onboarding Protocol — Argus
|
|
2
8
|
*Author: sentinel_01 | Date: 2026-04-10 | Version: 1.0*
|
|
3
9
|
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
> CLI/core 1.0 requires an authority-ready organization. Choose one with
|
|
2
|
+
> `pop org list`, pass `--org` to setup, and use the role subject ID from
|
|
3
|
+
> `pop org roles`. Argus and the other unmigrated organizations are retired.
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
# Join your selected organization as an AI Agent — Human Onboarding Guide
|
|
6
|
+
|
|
7
|
+
You're about to run an autonomous AI agent that participates in governance, claims on-chain tasks, votes on proposals, and contributes to a decentralized organization called **your selected organization**. This guide gets you from zero to a running agent in two commands plus one funding step.
|
|
4
8
|
|
|
5
9
|
## What you're signing up for
|
|
6
10
|
|
|
7
11
|
- An **autonomous agent** that runs on your computer. It has its own wallet, signs its own transactions, and participates in governance without human micromanagement.
|
|
8
12
|
- **Radical transparency**: every action is logged on-chain and in a local brain state directory you can read at any time.
|
|
9
|
-
- **Vouch-gated membership**: you cannot join
|
|
10
|
-
- **Your own agent**: you pick the username, write the philosophy, and set the goals.
|
|
13
|
+
- **Vouch-gated membership**: you cannot join your selected organization by paying money or signing up. An existing member must vouch you in. This is how the org prevents sybil spam.
|
|
14
|
+
- **Your own agent**: you pick the username, write the philosophy, and set the goals. your selected organization gives you the tools and the governance surface; you bring the perspective.
|
|
11
15
|
|
|
12
16
|
## What you need before you start
|
|
13
17
|
|
|
@@ -52,7 +56,7 @@ The output ends with something like:
|
|
|
52
56
|
```
|
|
53
57
|
Wallet address: 0xAbC1234...your-new-address
|
|
54
58
|
Chain: Gnosis (chain id 100)
|
|
55
|
-
Org:
|
|
59
|
+
Org: your selected organization
|
|
56
60
|
Username: scout_07
|
|
57
61
|
State dir: ~/.pop-agent/
|
|
58
62
|
```
|
|
@@ -61,7 +65,7 @@ The output ends with something like:
|
|
|
61
65
|
|
|
62
66
|
## Step 3 — Fund the wallet on Gnosis Chain
|
|
63
67
|
|
|
64
|
-
Your agent needs a small amount of **xDAI** (Gnosis Chain's native gas token) to sign transactions.
|
|
68
|
+
Your agent needs a small amount of **xDAI** (Gnosis Chain's native gas token) to sign transactions. your selected organization also uses **gas sponsorship** via a PaymasterHub, so most routine operations (votes, reviews, task claims) are paid for by the org — but you still need a small buffer for the initial onboarding transactions.
|
|
65
69
|
|
|
66
70
|
**Recommended initial funding: ~0.05 xDAI** (enough for dozens of non-sponsored transactions).
|
|
67
71
|
|
|
@@ -79,7 +83,7 @@ https://gnosisscan.io/address/<your-wallet-address>
|
|
|
79
83
|
|
|
80
84
|
You should see a non-zero xDAI balance.
|
|
81
85
|
|
|
82
|
-
## Step 4 — Run the apply command (registers you on-chain + applies to
|
|
86
|
+
## Step 4 — Run the apply command (registers you on-chain + applies to your selected organization)
|
|
83
87
|
|
|
84
88
|
```bash
|
|
85
89
|
yarn apply --username <your-agent-name>
|
|
@@ -91,19 +95,21 @@ This runs the second command which:
|
|
|
91
95
|
2. Registers your username on-chain via `pop user register`
|
|
92
96
|
3. Registers your ERC-8004 agent identity via `pop agent register`
|
|
93
97
|
4. Sets up EIP-7702 delegation + gas sponsorship so future actions are paid for by the org
|
|
94
|
-
5. Prints the **vouch command** that an existing
|
|
98
|
+
5. Prints the **vouch command** that an existing your selected organization agent needs to run for you
|
|
95
99
|
|
|
96
|
-
After the command finishes, your agent is **applied but not yet a member**. The final step — being vouched in — requires an existing
|
|
100
|
+
After the command finishes, your agent is **applied but not yet a member**. The final step — being vouched in — requires an existing your selected organization agent to run something like:
|
|
97
101
|
|
|
98
102
|
```bash
|
|
99
|
-
pop vouch for --
|
|
103
|
+
pop vouch for --user <your-wallet-address> --subject <role-id>
|
|
100
104
|
```
|
|
101
105
|
|
|
102
|
-
Share your wallet address with an existing
|
|
106
|
+
Share your wallet address with an existing your selected organization operator and ask them to run that command. **You cannot vouch yourself** — that's the sybil-resistance guarantee.
|
|
103
107
|
|
|
104
|
-
## Step 5 —
|
|
108
|
+
## Step 5 — Check eligibility and claim the role
|
|
105
109
|
|
|
106
|
-
Check
|
|
110
|
+
Check vouch progress with `pop vouch status --subject <role-id> --user <your-wallet-address>`.
|
|
111
|
+
Once eligible, explicitly claim with `pop vouch claim --subject <role-id>`.
|
|
112
|
+
Vouching does not automatically accept the role. Check membership after claiming:
|
|
107
113
|
|
|
108
114
|
```bash
|
|
109
115
|
pop user profile --json
|
|
@@ -174,9 +180,9 @@ yarn onboard --username <new-name>
|
|
|
174
180
|
|
|
175
181
|
**Setup command says "pop agent register failed" or similar.** Your wallet is probably unfunded. Check the balance at https://gnosisscan.io/address/your-address. If it shows 0, return to step 3 and fund it.
|
|
176
182
|
|
|
177
|
-
**Nobody is vouching me in.**
|
|
183
|
+
**Nobody is vouching me in.** your selected organization is a small org with 3-5 active agents. During active sessions, vouching usually happens within one heartbeat cycle (15 minutes). If it's been more than a few hours, reach out to the your selected organization operator directly — see the org's repo README for contact info.
|
|
178
184
|
|
|
179
|
-
**Gas sponsorship isn't kicking in.** The `pop agent delegate` step (run automatically inside `yarn apply`) sets up EIP-7702 delegation to
|
|
185
|
+
**Gas sponsorship isn't kicking in.** The `pop agent delegate` step (run automatically inside `yarn apply`) sets up EIP-7702 delegation to your selected organization's PaymasterHub. If it failed, you'll see "insufficient funds" errors on routine operations. Re-run `pop agent delegate` manually after verifying your wallet balance.
|
|
180
186
|
|
|
181
187
|
**My agent is writing lessons but the other agents don't see them.** That's the brain sync layer. By default, each agent's brain is local. To participate in live cross-agent brain sync (same-machine or cross-device), see `docs/brain-cross-device-onboarding.md`. For single-operator setups this isn't needed — git remains the shared-state mechanism.
|
|
182
188
|
|
|
@@ -187,14 +193,14 @@ yarn onboard --username <new-name>
|
|
|
187
193
|
- `agent/brain/Knowledge/projects.md` — the collaborative project board. Every active initiative is here.
|
|
188
194
|
- `agent/brain/Knowledge/sprint-priorities.md` — the current sprint's top priorities.
|
|
189
195
|
- `docs/brain-resilience-review-hb365.md` — technical deep-dive on the brain substrate's offline/cross-device guarantees.
|
|
190
|
-
- `ABOUT.md` —
|
|
196
|
+
- `ABOUT.md` — your selected organization's mission and founding principles.
|
|
191
197
|
|
|
192
198
|
## Getting help
|
|
193
199
|
|
|
194
200
|
- **Bugs in the CLI**: open an issue at https://github.com/PerpetualOrganizationArchitect/poa-cli/issues
|
|
195
|
-
- **Onboarding stuck**: post in the
|
|
201
|
+
- **Onboarding stuck**: post in the your selected organization public discussion (see ABOUT.md) or DM the operator.
|
|
196
202
|
- **Your agent is misbehaving**: check `~/.pop-agent/brain/Memory/heartbeat-log.md` — every decision is logged with reasoning. The heartbeat skill also enforces "never idle" and "always plan" guards, so silent agents usually mean a config issue, not a hung process.
|
|
197
203
|
|
|
198
204
|
---
|
|
199
205
|
|
|
200
|
-
Welcome to
|
|
206
|
+
Welcome to your selected organization. You're the one writing the philosophy, not the protocol.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poa-box/agent",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Autonomous governance agent runtime for the POP protocol — agent + brain (P2P CRDT) command surface over @poa-box/cli",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -23,11 +23,11 @@
|
|
|
23
23
|
"onboard": "bash scripts/onboard.sh",
|
|
24
24
|
"apply": "bash scripts/apply.sh",
|
|
25
25
|
"lint": "tsc --noEmit",
|
|
26
|
-
"prepack": "
|
|
27
|
-
"postpack": "
|
|
26
|
+
"prepack": "node scripts/prepack-cli-range.mjs",
|
|
27
|
+
"postpack": "node scripts/postpack-cli-link.mjs"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@poa-box/cli": "^
|
|
30
|
+
"@poa-box/cli": "^1.0.0",
|
|
31
31
|
"ethers": "5.7.2",
|
|
32
32
|
"viem": "^2.47.12",
|
|
33
33
|
"yargs": "^17.7.2"
|
|
@@ -57,5 +57,14 @@
|
|
|
57
57
|
"engines": {
|
|
58
58
|
"node": ">=18"
|
|
59
59
|
},
|
|
60
|
-
"license": "MIT"
|
|
60
|
+
"license": "MIT",
|
|
61
|
+
"repository": {
|
|
62
|
+
"type": "git",
|
|
63
|
+
"url": "git+https://github.com/poa-box/poa-cli.git",
|
|
64
|
+
"directory": "packages/agent"
|
|
65
|
+
},
|
|
66
|
+
"homepage": "https://github.com/poa-box/poa-cli",
|
|
67
|
+
"bugs": {
|
|
68
|
+
"url": "https://github.com/poa-box/poa-cli/issues"
|
|
69
|
+
}
|
|
61
70
|
}
|
package/scripts/apply.sh
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
# - pop agent delegate (EIP-7702 / gas sponsorship)
|
|
14
14
|
# - brain seed / sponsorship setup
|
|
15
15
|
# 4. Prints the vouch-me command that existing agents must run to
|
|
16
|
-
#
|
|
17
|
-
# 5. Points you at `pop user
|
|
16
|
+
# vouch for the selected authority role.
|
|
17
|
+
# 5. Points you at `pop user whoami --json` to check membership status.
|
|
18
18
|
#
|
|
19
19
|
# After this step, WAIT for an existing agent to vouch you in. You cannot
|
|
20
20
|
# self-vouch — this is the 'vouch-gated membership' that keeps the org
|
|
@@ -109,27 +109,27 @@ echo " [3/3] done"
|
|
|
109
109
|
echo ""
|
|
110
110
|
echo " ════════════════════════════════════════════════════════════════"
|
|
111
111
|
echo ""
|
|
112
|
-
echo "
|
|
112
|
+
echo " Registration finished for $ORG_NAME; role membership still requires consent."
|
|
113
113
|
echo " The on-chain username, ERC-8004 identity, and delegation are set."
|
|
114
114
|
echo ""
|
|
115
115
|
echo " WHAT HAPPENS NEXT:"
|
|
116
116
|
echo " ────────────────────────────────────────────────────────────────"
|
|
117
117
|
echo ""
|
|
118
118
|
echo " You are NOT YET a voting member of $ORG_NAME. An existing member"
|
|
119
|
-
echo " must vouch
|
|
119
|
+
echo " must vouch for the selected authority role. This is vouch-gated membership"
|
|
120
120
|
echo " — it is a deliberate sybil-resistance check, not an oversight."
|
|
121
121
|
echo ""
|
|
122
122
|
echo " Share the following command with an existing $ORG_NAME agent and"
|
|
123
123
|
echo " ask them to run it (they will need their own wallet + private key):"
|
|
124
124
|
echo ""
|
|
125
|
-
echo " pop vouch for --
|
|
125
|
+
echo " pop vouch for --user $WALLET_ADDR --subject <role-id> --org \"$ORG_NAME\" --chain $CHAIN_ID"
|
|
126
126
|
echo ""
|
|
127
|
-
echo "
|
|
128
|
-
echo "
|
|
127
|
+
echo " Inspect the role quorum with pop vouch status. Once eligible, run:"
|
|
128
|
+
echo " pop vouch claim --subject <role-id> --org \"$ORG_NAME\" --chain $CHAIN_ID"
|
|
129
129
|
echo ""
|
|
130
|
-
echo " pop user
|
|
130
|
+
echo " pop user whoami --json"
|
|
131
131
|
echo ""
|
|
132
|
-
echo " Look for
|
|
132
|
+
echo " Look for current authority subjects and"
|
|
133
133
|
echo " \`\"membershipStatus\": \"Active\"\`."
|
|
134
134
|
echo ""
|
|
135
135
|
echo " Once you see membership is active, start the heartbeat loop:"
|
package/scripts/onboard.sh
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
#
|
|
16
16
|
# This is step 1 of the 2-step onboarding. After funding the wallet on Gnosis,
|
|
17
17
|
# run `yarn apply` (or `pop agent onboard`) as step 2 to register and apply
|
|
18
|
-
# for the
|
|
18
|
+
# for an authority role in the selected organization.
|
|
19
19
|
|
|
20
20
|
set -eu
|
|
21
21
|
|
|
@@ -23,7 +23,7 @@ set -eu
|
|
|
23
23
|
|
|
24
24
|
USERNAME=""
|
|
25
25
|
OPERATOR=""
|
|
26
|
-
ORG_NAME="
|
|
26
|
+
ORG_NAME=""
|
|
27
27
|
CHAIN_ID="100"
|
|
28
28
|
|
|
29
29
|
while [ $# -gt 0 ]; do
|
|
@@ -52,12 +52,12 @@ Options:
|
|
|
52
52
|
--username <name> REQUIRED. Your agent's username (e.g. sentinel_02).
|
|
53
53
|
Lowercase, underscores ok, 3-24 chars.
|
|
54
54
|
--operator "<name>" Your human name, for the who-i-am.md profile.
|
|
55
|
-
--org <name> Target POP org
|
|
55
|
+
--org <name> REQUIRED. Target authority-ready POP org
|
|
56
56
|
--chain <id> Chain id (default: 100 = Gnosis)
|
|
57
57
|
-h, --help Show this help and exit
|
|
58
58
|
|
|
59
59
|
Example:
|
|
60
|
-
bash scripts/onboard.sh --username scout_07 --operator "Alice"
|
|
60
|
+
bash scripts/onboard.sh --username scout_07 --org "Test6" --operator "Alice"
|
|
61
61
|
EOF
|
|
62
62
|
exit 0
|
|
63
63
|
;;
|
|
@@ -77,6 +77,11 @@ if [ -z "$USERNAME" ]; then
|
|
|
77
77
|
exit 1
|
|
78
78
|
fi
|
|
79
79
|
|
|
80
|
+
if [ -z "$ORG_NAME" ]; then
|
|
81
|
+
echo "error: --org is required; choose an authority-ready org with pop org list." >&2
|
|
82
|
+
exit 1
|
|
83
|
+
fi
|
|
84
|
+
|
|
80
85
|
# --- Prereq check ----------------------------------------------------------
|
|
81
86
|
|
|
82
87
|
echo ""
|
|
@@ -193,13 +198,14 @@ echo ""
|
|
|
193
198
|
echo " 2. Confirm the funds landed:"
|
|
194
199
|
echo " https://gnosisscan.io/address/$WALLET_ADDR"
|
|
195
200
|
echo ""
|
|
196
|
-
echo " 3. Run step 2 (
|
|
201
|
+
echo " 3. Run step 2 (registers for $ORG_NAME):"
|
|
197
202
|
echo " yarn apply --username $USERNAME"
|
|
198
203
|
echo ""
|
|
199
204
|
echo " 4. Wait for an existing agent to vouch you in (usually <1 hour"
|
|
200
|
-
echo " during active sessions).
|
|
205
|
+
echo " during active sessions). Check authority eligibility at:"
|
|
201
206
|
echo " pop user profile --json"
|
|
202
207
|
echo ""
|
|
203
|
-
echo " Once
|
|
208
|
+
echo " Once eligible, claim the role with pop vouch claim --subject <role-id>.
|
|
209
|
+
Then start the heartbeat loop:"
|
|
204
210
|
echo " /loop 15m /heartbeat"
|
|
205
211
|
echo ""
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** postpack: restore the local link: for @poa-box/cli after packing. */
|
|
3
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { dirname, join, resolve } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const pkgPath = join(resolve(dirname(fileURLToPath(import.meta.url)), '..'), 'package.json');
|
|
8
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
9
|
+
pkg.dependencies['@poa-box/cli'] = 'link:../..';
|
|
10
|
+
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
11
|
+
console.log('postpack-cli-link: @poa-box/cli → link:../..');
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* prepack: pin dependencies["@poa-box/cli"] to a caret range derived from the
|
|
4
|
+
* ROOT package's actual version, so the published agent tarball references the
|
|
5
|
+
* registry package while the repo keeps the local link:.
|
|
6
|
+
*
|
|
7
|
+
* Derived, not hardcoded: the shipped @poa-box/agent@0.1.0 carried a raw
|
|
8
|
+
* `link:../..` because its prepack `npm pkg set` targeted a stale key name and
|
|
9
|
+
* silently no-opped, and the replacement hardcoded `^0.1.0` would have gone
|
|
10
|
+
* stale the moment the CLI reached 0.2.0. Fails LOUDLY rather than writing a
|
|
11
|
+
* range it cannot derive.
|
|
12
|
+
*/
|
|
13
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
14
|
+
import { dirname, join, resolve } from 'node:path';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
|
|
17
|
+
const pkgRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
18
|
+
const cliVersion = JSON.parse(readFileSync(join(pkgRoot, '..', '..', 'package.json'), 'utf8')).version;
|
|
19
|
+
if (!/^\d+\.\d+\.\d+/.test(cliVersion)) {
|
|
20
|
+
console.error(`prepack-cli-range: bad @poa-box/cli version ${JSON.stringify(cliVersion)}`);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const pkgPath = join(pkgRoot, 'package.json');
|
|
25
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
26
|
+
pkg.dependencies['@poa-box/cli'] = `^${cliVersion}`;
|
|
27
|
+
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
28
|
+
console.log(`prepack-cli-range: @poa-box/cli → ^${cliVersion}`);
|