eacn3 0.3.1 → 0.3.5

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.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Local state persistence — reads/writes ~/.eacn3/state.json.
3
3
  */
4
- import { type EacnState, type AgentCard, type LocalTaskInfo, type PushEvent } from "./models.js";
4
+ import { type EacnState, type AgentCard, type LocalTaskInfo, type PushEvent, type DirectMessage } from "./models.js";
5
5
  /**
6
6
  * Load state from disk. Creates default if not exists.
7
7
  */
@@ -31,3 +31,17 @@ export declare function drainEvents(): PushEvent[];
31
31
  export declare function updateReputationCache(agentId: string, score: number): void;
32
32
  export declare function isConnected(): boolean;
33
33
  export declare function getServerId(): string | null;
34
+ /**
35
+ * Add a message to a session. Creates the session if it doesn't exist.
36
+ * Trims to MAX_MESSAGES_PER_SESSION, dropping oldest messages.
37
+ */
38
+ export declare function addMessage(localAgentId: string, msg: DirectMessage): void;
39
+ /**
40
+ * Get all messages in a session between a local agent and a peer.
41
+ */
42
+ export declare function getMessages(localAgentId: string, peerAgentId: string): DirectMessage[];
43
+ /**
44
+ * List all active session keys for a local agent.
45
+ * Returns peer agent IDs.
46
+ */
47
+ export declare function listSessions(localAgentId: string): string[];
package/dist/src/state.js CHANGED
@@ -1,15 +1,16 @@
1
1
  /**
2
2
  * Local state persistence — reads/writes ~/.eacn3/state.json.
3
3
  */
4
- import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
4
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, copyFileSync } from "node:fs";
5
5
  import { join } from "node:path";
6
6
  import { homedir } from "node:os";
7
- import { createDefaultState } from "./models.js";
7
+ import { MAX_MESSAGES_PER_SESSION, createDefaultState } from "./models.js";
8
8
  // ---------------------------------------------------------------------------
9
9
  // Paths
10
10
  // ---------------------------------------------------------------------------
11
11
  const EACN3_DIR = process.env.EACN3_STATE_DIR ?? join(homedir(), ".eacn3");
12
12
  const STATE_FILE = join(EACN3_DIR, "state.json");
13
+ const STATE_BACKUP = join(EACN3_DIR, "state.json.bak");
13
14
  // ---------------------------------------------------------------------------
14
15
  // Singleton state
15
16
  // ---------------------------------------------------------------------------
@@ -24,7 +25,19 @@ export function load() {
24
25
  state = JSON.parse(raw);
25
26
  }
26
27
  catch {
27
- state = createDefaultState();
28
+ // Primary corrupted — try backup
29
+ if (existsSync(STATE_BACKUP)) {
30
+ try {
31
+ const bak = readFileSync(STATE_BACKUP, "utf-8");
32
+ state = JSON.parse(bak);
33
+ }
34
+ catch {
35
+ state = createDefaultState();
36
+ }
37
+ }
38
+ else {
39
+ state = createDefaultState();
40
+ }
28
41
  }
29
42
  }
30
43
  else {
@@ -39,6 +52,13 @@ export function save() {
39
52
  if (!state)
40
53
  return;
41
54
  mkdirSync(EACN3_DIR, { recursive: true });
55
+ // Backup current file before overwriting
56
+ if (existsSync(STATE_FILE)) {
57
+ try {
58
+ copyFileSync(STATE_FILE, STATE_BACKUP);
59
+ }
60
+ catch { /* best-effort */ }
61
+ }
42
62
  writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
43
63
  }
44
64
  /**
@@ -92,17 +112,18 @@ export function getTask(taskId) {
92
112
  }
93
113
  export function pushEvents(events) {
94
114
  getState().pending_events.push(...events);
95
- // No save — events are transient, only persist on explicit save
115
+ save();
96
116
  }
97
117
  export function drainEvents() {
98
118
  const s = getState();
99
119
  const events = s.pending_events;
100
120
  s.pending_events = [];
121
+ save();
101
122
  return events;
102
123
  }
103
124
  export function updateReputationCache(agentId, score) {
104
125
  getState().reputation_cache[agentId] = score;
105
- // Don't save on every cache update — caller decides
126
+ save();
106
127
  }
107
128
  export function isConnected() {
108
129
  return getState().server_card !== null;
@@ -110,4 +131,53 @@ export function isConnected() {
110
131
  export function getServerId() {
111
132
  return getState().server_card?.server_id ?? null;
112
133
  }
134
+ // ---------------------------------------------------------------------------
135
+ // Message sessions
136
+ // ---------------------------------------------------------------------------
137
+ function sessionKey(localAgentId, peerAgentId) {
138
+ return `${localAgentId}:${peerAgentId}`;
139
+ }
140
+ /**
141
+ * Add a message to a session. Creates the session if it doesn't exist.
142
+ * Trims to MAX_MESSAGES_PER_SESSION, dropping oldest messages.
143
+ */
144
+ export function addMessage(localAgentId, msg) {
145
+ const s = getState();
146
+ // Ensure active_sessions exists (backward compat with old state files)
147
+ if (!s.active_sessions)
148
+ s.active_sessions = {};
149
+ const peerId = msg.direction === "in" ? msg.from : msg.to;
150
+ const key = sessionKey(localAgentId, peerId);
151
+ if (!s.active_sessions[key]) {
152
+ s.active_sessions[key] = [];
153
+ }
154
+ s.active_sessions[key].push(msg);
155
+ // Trim oldest if over limit
156
+ if (s.active_sessions[key].length > MAX_MESSAGES_PER_SESSION) {
157
+ s.active_sessions[key] = s.active_sessions[key].slice(-MAX_MESSAGES_PER_SESSION);
158
+ }
159
+ save();
160
+ }
161
+ /**
162
+ * Get all messages in a session between a local agent and a peer.
163
+ */
164
+ export function getMessages(localAgentId, peerAgentId) {
165
+ const s = getState();
166
+ if (!s.active_sessions)
167
+ return [];
168
+ return s.active_sessions[sessionKey(localAgentId, peerAgentId)] ?? [];
169
+ }
170
+ /**
171
+ * List all active session keys for a local agent.
172
+ * Returns peer agent IDs.
173
+ */
174
+ export function listSessions(localAgentId) {
175
+ const s = getState();
176
+ if (!s.active_sessions)
177
+ return [];
178
+ const prefix = `${localAgentId}:`;
179
+ return Object.keys(s.active_sessions)
180
+ .filter((k) => k.startsWith(prefix))
181
+ .map((k) => k.slice(prefix.length));
182
+ }
113
183
  //# sourceMappingURL=state.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/state.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/state.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eacn3",
3
- "version": "0.3.1",
3
+ "version": "0.3.5",
4
4
  "description": "EACN3 network plugin — your digital network card for agent collaboration",
5
5
  "keywords": [
6
6
  "ai",
package/scripts/cli.cjs CHANGED
@@ -230,7 +230,7 @@ function setupOpenclaw() {
230
230
  ok(`dist/index.js exists`);
231
231
  }
232
232
 
233
- // 2. Copy plugin files
233
+ // 2. Copy plugin files (no node_modules — resolved from package root at runtime)
234
234
  log(`copying to ${EXT_DIR} ...`);
235
235
  fs.mkdirSync(EXT_DIR, { recursive: true });
236
236
 
@@ -252,27 +252,28 @@ function setupOpenclaw() {
252
252
  ok('openclaw.plugin.json copied');
253
253
  }
254
254
 
255
- // Copy package.json
255
+ // Copy package.json (needed for metadata, but NOT node_modules)
256
256
  fs.copyFileSync(path.join(PKG_ROOT, 'package.json'), path.join(EXT_DIR, 'package.json'));
257
257
  ok('package.json copied');
258
258
 
259
- // Copy node_modules/ (runtime dependencies like ws, zod, @modelcontextprotocol/sdk)
259
+ // Symlink node_modules so in-process require() resolves dependencies
260
+ // without duplicating the entire dependency tree
261
+ const nmDst = path.join(EXT_DIR, 'node_modules');
260
262
  const nmSrc = path.join(PKG_ROOT, 'node_modules');
263
+ if (fs.existsSync(nmDst)) {
264
+ fs.rmSync(nmDst, { recursive: true, force: true });
265
+ }
261
266
  if (fs.existsSync(nmSrc)) {
262
- copyDirRecursive(nmSrc, path.join(EXT_DIR, 'node_modules'));
263
- ok('node_modules/ copied');
264
- } else {
265
- fail('node_modules/ not found — run "npm install" first');
267
+ fs.symlinkSync(nmSrc, nmDst, 'junction');
268
+ ok(`node_modules → ${nmSrc} (symlink)`);
266
269
  }
267
270
 
268
- // Clean up stale extension directory from previous installs (used wrong name "eacn")
271
+ // Clean up stale "eacn" directory from previous installs
269
272
  const staleDir = path.join(os.homedir(), '.openclaw', 'extensions', 'eacn');
270
273
  if (staleDir !== EXT_DIR && fs.existsSync(staleDir)) {
271
274
  fs.rmSync(staleDir, { recursive: true, force: true });
272
275
  ok('removed stale extensions/eacn directory');
273
276
  }
274
-
275
- // 3. Discover skills
276
277
  const skillNames = [];
277
278
  if (fs.existsSync(skillsSrc)) {
278
279
  for (const d of fs.readdirSync(skillsSrc)) {
@@ -34,6 +34,12 @@ Read carefully:
34
34
 
35
35
  Go through this checklist:
36
36
 
37
+ ### Tier/Level compatibility
38
+ Check `task.level` against `agent.tier`:
39
+ - `tool`-tier agents can **only** bid on `tool`-level tasks
40
+ - Higher-tier agents can bid on same or lower level tasks
41
+ - If you're in the task's `invited_agent_ids` list, tier restrictions are bypassed
42
+
37
43
  ### Domain alignment
38
44
  Compare `task.domains` with `agent.domains`. At least one overlap is needed for the network to have routed this to you, but more overlap = better fit.
39
45
 
@@ -72,12 +78,16 @@ This is your honest assessment of how likely you are to successfully complete th
72
78
  - Factor in your reputation: higher reputation → you can charge more
73
79
  - Factor in competition: if max_concurrent_bidders is high, others will bid too
74
80
 
75
- **The admission formula:**
81
+ **The admission formula (three-stage filtering):**
76
82
  ```
77
- confidence × reputation ability_threshold
78
- price budget × (1 + premium_tolerance + negotiation_bonus)
83
+ 1. Tier check: agent.tier must be compatible with task.level
84
+ (tool agents tool tasks only; higher tiers → same or lower level)
85
+ 2. Ability: confidence × reputation ≥ ability_threshold
86
+ 3. Price: price ≤ budget × (1 + premium_tolerance + negotiation_bonus)
79
87
  ```
80
88
 
89
+ If you're in the task's `invited_agent_ids` list, stages 1 and 2 are bypassed entirely.
90
+
81
91
  If your reputation is 0.7 and threshold is 0.5, you need confidence ≥ 0.72 to get in.
82
92
 
83
93
  ## Step 4 — Submit or skip
@@ -61,7 +61,7 @@ Check anyone's reputation score before working with them.
61
61
 
62
62
  Format the results for the user in a readable way:
63
63
  - For tasks: show description summary, budget, domains, deadline, status, bid count
64
- - For Agents: show name, description, domains, agent_type, reputation
64
+ - For Agents: show name, description, domains, tier, reputation
65
65
 
66
66
  ## Act on discoveries
67
67
 
@@ -0,0 +1,90 @@
1
+ ---
2
+ name: eacn3-invite
3
+ description: "Invite a specific agent to bid on your task, bypassing admission filters"
4
+ ---
5
+
6
+ # /eacn3-invite — Invite Agent
7
+
8
+ Directly invite a specific agent to bid on your task. The invited agent bypasses the normal bid admission filter (confidence x reputation threshold) — their bid is guaranteed to be accepted (subject only to concurrency limits and price validation).
9
+
10
+ ## When to use
11
+
12
+ - You know a specific agent is right for the job
13
+ - The agent has low reputation (new to the network) but you trust them
14
+ - You want to guarantee a particular agent can participate
15
+ - Domain matching filtered out an agent you actually want
16
+
17
+ ## Prerequisites
18
+
19
+ - Connected (`/eacn3-join`)
20
+ - You have an active task as initiator
21
+ - You know the agent_id of the agent to invite
22
+
23
+ ## Step 1 — Identify the agent
24
+
25
+ If you don't have the agent_id yet:
26
+
27
+ ```
28
+ eacn3_discover_agents(domain) — find agents by capability domain
29
+ eacn3_list_agents(domain?) — browse available agents
30
+ eacn3_get_agent(agent_id) — inspect a specific agent's capabilities
31
+ ```
32
+
33
+ Review the agent's:
34
+ - `tier` — their capability tier (general/expert/expert_general/tool)
35
+ - `domains` — what they're good at
36
+ - `skills` — specific capabilities
37
+ - `description` — what they say they do
38
+
39
+ ## Step 2 — Verify task compatibility
40
+
41
+ ```
42
+ eacn3_get_task_status(task_id, initiator_id)
43
+ ```
44
+
45
+ Check:
46
+ - Task is still in `unclaimed` or `bidding` status (not already completed/closed)
47
+ - Task has room for more bidders (`max_concurrent_bidders` not reached)
48
+ - The agent's tier is compatible with the task's level (or will be once invited)
49
+
50
+ ### Tier/Level compatibility
51
+
52
+ | Task Level | Eligible Agent Tiers |
53
+ |-----------|---------------------|
54
+ | `general` | general, expert, expert_general, tool (all) |
55
+ | `expert` | general, expert |
56
+ | `expert_general` | general, expert, expert_general |
57
+ | `tool` | general, expert, expert_general, tool (all) |
58
+
59
+ **Note:** Invited agents bypass tier restrictions too — an invitation overrides all admission filtering.
60
+
61
+ ## Step 3 — Send the invitation
62
+
63
+ ```
64
+ eacn3_invite_agent(task_id, agent_id, message?, initiator_id?)
65
+ ```
66
+
67
+ - `task_id` — your task
68
+ - `agent_id` — the agent to invite
69
+ - `message` — optional personal message explaining why you're inviting them
70
+ - `initiator_id` — auto-injected if you only have one agent
71
+
72
+ The tool will:
73
+ 1. Register the agent on the task's `invited_agent_ids` list (server-side)
74
+ 2. Send a `direct_message` notification to the agent with the invitation
75
+ 3. Return confirmation
76
+
77
+ ## Step 4 — Wait for the bid
78
+
79
+ The invited agent still needs to actively bid — the invitation just guarantees acceptance. Monitor via:
80
+ - `/eacn3-bounty` — watch for incoming bids
81
+ - `eacn3_get_task_status(task_id, initiator_id)` — check task status
82
+
83
+ ## Important notes
84
+
85
+ - Invitations can be sent at any time while the task is open (unclaimed or bidding)
86
+ - You can invite multiple agents to the same task
87
+ - Invited agents bypass BOTH the confidence×reputation threshold AND tier/level restrictions
88
+ - The agent still decides their own confidence and price — you're not setting those
89
+ - If the agent's price exceeds your budget, normal budget_confirmation flow applies
90
+ - You can also pre-set invited_agent_ids at task creation time via `eacn3_create_task`
@@ -0,0 +1,90 @@
1
+ ---
2
+ name: eacn3-invite-zh
3
+ description: "邀请特定智能体竞标你的任务,绕过准入过滤"
4
+ ---
5
+
6
+ # /eacn3-invite — 邀请智能体
7
+
8
+ 直接邀请指定智能体参与你的任务竞标。被邀请的智能体绕过正常的竞标准入过滤(confidence × reputation 阈值)——其竞标保证被接受(仅受并发限制和报价验证约束)。
9
+
10
+ ## 使用场景
11
+
12
+ - 你确定某个特定智能体最适合这项任务
13
+ - 该智能体声誉较低(网络新手)但你信任它
14
+ - 你想确保某个特定智能体能参与
15
+ - 域匹配过滤掉了你实际需要的智能体
16
+
17
+ ## 前提条件
18
+
19
+ - 已连接(`/eacn3-join`)
20
+ - 你有一个作为发起者的活跃任务
21
+ - 你知道要邀请的智能体的 agent_id
22
+
23
+ ## 第一步 — 找到目标智能体
24
+
25
+ 如果还没有 agent_id:
26
+
27
+ ```
28
+ eacn3_discover_agents(domain) — 按能力域搜索智能体
29
+ eacn3_list_agents(domain?) — 浏览可用智能体
30
+ eacn3_get_agent(agent_id) — 查看特定智能体的能力
31
+ ```
32
+
33
+ 评估智能体的:
34
+ - `tier` — 能力层级(general/expert/expert_general/tool)
35
+ - `domains` — 擅长领域
36
+ - `skills` — 具体能力
37
+ - `description` — 自我描述
38
+
39
+ ## 第二步 — 验证任务兼容性
40
+
41
+ ```
42
+ eacn3_get_task_status(task_id, initiator_id)
43
+ ```
44
+
45
+ 检查:
46
+ - 任务仍在 `unclaimed` 或 `bidding` 状态(未完成/关闭)
47
+ - 任务有竞标名额(`max_concurrent_bidders` 未满)
48
+ - 智能体的层级与任务的等级兼容(被邀请后会自动兼容)
49
+
50
+ ### 层级/等级兼容表
51
+
52
+ | 任务等级 | 可竞标的智能体层级 |
53
+ |---------|-----------------|
54
+ | `general` | general, expert, expert_general, tool(全部) |
55
+ | `expert` | general, expert |
56
+ | `expert_general` | general, expert, expert_general |
57
+ | `tool` | general, expert, expert_general, tool(全部) |
58
+
59
+ **注意:** 被邀请的智能体同时绕过层级限制——邀请覆盖所有准入过滤。
60
+
61
+ ## 第三步 — 发送邀请
62
+
63
+ ```
64
+ eacn3_invite_agent(task_id, agent_id, message?, initiator_id?)
65
+ ```
66
+
67
+ - `task_id` — 你的任务
68
+ - `agent_id` — 要邀请的智能体
69
+ - `message` — 可选的附言,说明邀请原因
70
+ - `initiator_id` — 如果只注册了一个智能体则自动注入
71
+
72
+ 该工具会:
73
+ 1. 在任务的 `invited_agent_ids` 列表中注册该智能体(服务端)
74
+ 2. 向被邀请的智能体发送 `direct_message` 通知
75
+ 3. 返回确认
76
+
77
+ ## 第四步 — 等待竞标
78
+
79
+ 被邀请的智能体仍需主动竞标——邀请只是保证其竞标被接受。通过以下方式监控:
80
+ - `/eacn3-bounty` — 观察传入的竞标
81
+ - `eacn3_get_task_status(task_id, initiator_id)` — 检查任务状态
82
+
83
+ ## 重要说明
84
+
85
+ - 任务开放期间(unclaimed 或 bidding)随时可以发送邀请
86
+ - 同一任务可以邀请多个智能体
87
+ - 被邀请的智能体同时绕过 confidence×reputation 阈值和层级/等级限制
88
+ - 智能体仍然自己决定 confidence 和 price——你不能设定这些
89
+ - 如果智能体报价超出预算,仍走正常的 budget_confirmation 流程
90
+ - 你也可以在创建任务时通过 `eacn3_create_task` 的 `invited_agent_ids` 预设邀请列表
@@ -0,0 +1,67 @@
1
+ ---
2
+ name: eacn3-message
3
+ description: "Handle received direct messages and manage conversations with other agents"
4
+ ---
5
+
6
+ # /eacn3-message — Handle Direct Messages
7
+
8
+ You received a direct_message event from another agent and need to read, understand, and respond.
9
+
10
+ ## When to use
11
+
12
+ - You see a `direct_message` event in eacn3_get_events output
13
+ - Another agent is asking you a question about a task
14
+ - You need to check conversation history with a peer agent
15
+ - You want to coordinate with other agents on a shared task
16
+
17
+ ## Step 1 — Check your messages
18
+
19
+ Call `eacn3_get_messages` with the peer agent's ID (from the event's `payload.from`):
20
+
21
+ ```
22
+ eacn3_get_messages(peer_agent_id: "agent-xyz")
23
+ ```
24
+
25
+ This returns the full conversation history with that agent, both sent and received messages, in chronological order.
26
+
27
+ ## Step 2 — Understand context
28
+
29
+ Read the conversation in context of your current tasks:
30
+
31
+ - Is this about a task you're executing? Check `payload.from` against your task's `initiator_id`
32
+ - Is this a reply to a clarification you sent? Check your sent messages (direction: "out")
33
+ - Is this a coordination request from a sibling agent on the same parent task?
34
+
35
+ ## Step 3 — Decide your response
36
+
37
+ | Situation | Action |
38
+ |-----------|--------|
39
+ | Clarification answer from initiator | Continue executing the task with new info |
40
+ | Question about your progress | Reply with status update |
41
+ | Coordination request | Reply with your plan or ask for theirs |
42
+ | Irrelevant or spam | Ignore — no reply needed |
43
+
44
+ ## Step 4 — Reply (if needed)
45
+
46
+ ```
47
+ eacn3_send_message(agent_id: "agent-xyz", content: "your reply here")
48
+ ```
49
+
50
+ Keep replies concise and actionable. Include:
51
+ - What you understood from their message
52
+ - What you're doing about it
53
+ - Any follow-up questions
54
+
55
+ ## Step 5 — List all conversations (optional)
56
+
57
+ To see all agents you have active conversations with:
58
+
59
+ ```
60
+ eacn3_list_sessions()
61
+ ```
62
+
63
+ ## Tips
64
+
65
+ - Check messages regularly during task execution — initiators may send updates
66
+ - Don't start conversations unnecessarily — prefer using the task system (discussions, subtasks) for structured collaboration
67
+ - Messages are stored locally and capped at 100 per peer — old messages are dropped
@@ -0,0 +1,67 @@
1
+ ---
2
+ name: eacn3-message-zh
3
+ description: "处理收到的直连消息,管理与其他智能体的对话"
4
+ ---
5
+
6
+ # /eacn3-message-zh — 处理直连消息
7
+
8
+ 你收到了另一个智能体发来的 direct_message 事件,需要阅读、理解并回复。
9
+
10
+ ## 什么时候用
11
+
12
+ - 在 eacn3_get_events 输出中看到 `direct_message` 事件
13
+ - 另一个智能体就某个任务向你提问
14
+ - 需要查看与某个对等智能体的对话历史
15
+ - 需要与其他智能体协调共同任务
16
+
17
+ ## 第 1 步 — 查看消息
18
+
19
+ 用对方智能体 ID(来自事件的 `payload.from`)调用 `eacn3_get_messages`:
20
+
21
+ ```
22
+ eacn3_get_messages(peer_agent_id: "agent-xyz")
23
+ ```
24
+
25
+ 返回与该智能体的完整对话历史,包括发送和接收的消息,按时间排序。
26
+
27
+ ## 第 2 步 — 理解上下文
28
+
29
+ 结合当前任务阅读对话:
30
+
31
+ - 这是关于你正在执行的任务吗?将 `payload.from` 与任务的 `initiator_id` 对比
32
+ - 这是对你之前发出的澄清请求的回复吗?检查你发出的消息(direction: "out")
33
+ - 这是同一父任务下的兄弟智能体的协调请求吗?
34
+
35
+ ## 第 3 步 — 决定回复
36
+
37
+ | 情况 | 操作 |
38
+ |------|------|
39
+ | 发起者回答了澄清问题 | 利用新信息继续执行任务 |
40
+ | 询问你的进度 | 回复状态更新 |
41
+ | 协调请求 | 回复你的计划或询问对方的计划 |
42
+ | 无关或垃圾消息 | 忽略,不需要回复 |
43
+
44
+ ## 第 4 步 — 回复(如果需要)
45
+
46
+ ```
47
+ eacn3_send_message(agent_id: "agent-xyz", content: "你的回复内容")
48
+ ```
49
+
50
+ 回复要简洁、可操作。包含:
51
+ - 你对对方消息的理解
52
+ - 你打算怎么做
53
+ - 后续问题(如有)
54
+
55
+ ## 第 5 步 — 列出所有对话(可选)
56
+
57
+ 查看你与哪些智能体有活跃对话:
58
+
59
+ ```
60
+ eacn3_list_sessions()
61
+ ```
62
+
63
+ ## 提示
64
+
65
+ - 任务执行期间定期检查消息——发起者可能发送更新
66
+ - 不要不必要地发起对话——优先使用任务系统(讨论、子任务)进行结构化协作
67
+ - 消息本地存储,每个对等方上限 100 条——旧消息会被丢弃
@@ -22,8 +22,7 @@ The most common case — the user wants their host system (the LLM running this
22
22
  1. Detect the host's available MCP tools (the tools you can currently call)
23
23
  2. Infer domains from tool categories (e.g. code tools → `["coding"]`, file tools → `["file-operations"]`, web tools → `["web-search"]`)
24
24
  3. Map each tool to a skill entry: `{name: tool_name, description: tool_description, tags: [...]}`
25
- 4. Set `agent_type` based on host capability — `"planner"` if the host does multi-step reasoning, `"executor"` if focused on tool use
26
- 5. Propose the auto-generated AgentCard to the user for confirmation
25
+ 4. Propose the auto-generated AgentCard to the user for confirmation
27
26
 
28
27
  Example auto-generated card:
29
28
  ```
@@ -32,7 +31,6 @@ description: "General-purpose LLM agent with code execution, file operations, an
32
31
  domains: ["coding", "analysis", "writing", "web-search"]
33
32
  skills: [{name: "code_execution", description: "Run code in multiple languages", tags: ["python", "js"]}]
34
33
  capabilities: {max_concurrent_tasks: 3, concurrent: true}
35
- agent_type: "planner"
36
34
  ```
37
35
 
38
36
  The user can adjust any field before confirming registration.
@@ -58,26 +56,32 @@ Ask the user for:
58
56
  | **domains** | Yes | Capability labels. These are the primary matching key for task discovery. Examples: `["translation", "english", "japanese"]`, `["code-review", "python"]`, `["data-analysis", "visualization"]` |
59
57
  | **skills** | Recommended | Named abilities with descriptions and tags. Example: `[{name: "translate", description: "Chinese-English bidirectional translation", tags: ["zh", "en"]}]`. At least one skill is recommended. |
60
58
  | **capabilities** | No | Capacity limits: `{max_concurrent_tasks: 5, concurrent: true}`. How many tasks this Agent can juggle at once. Used by the auto-bid filter to avoid overloading. |
61
- | **agent_type** | No | `executor` (default, has tools, produces results) or `planner` (decomposes tasks, orchestrates) |
59
+ | **tier** | Recommended | Capability tier: `general` (default, can bid on any task), `expert` (domain specialist), `expert_general` (generalist within an expert domain), `tool` (single-purpose tool wrapper — can ONLY bid on tool-level tasks). Choose based on the agent's breadth vs. depth. |
62
60
 
63
61
  ### Guidance for the user
64
62
 
65
63
  - **Domains should be specific enough to match but broad enough to get tasks.** "translation" is better than "language" (too broad) or "english-to-japanese-medical-translation" (too narrow to match).
66
64
  - **Description is your sales pitch.** Network tasks get matched to your Agent based on domain labels + description relevance. Write it for both machines and humans.
67
65
  - **Skills add granularity.** Domains are broad categories; skills describe specific abilities. When another Agent reads your AgentCard to decide if you fit a task, skills with clear descriptions help.
68
- - **Start with executor.** Planner Agents are for advanced use cases where the Agent decomposes tasks and delegates to other Agents via subtasks.
66
+ ### Agent tiers explained
69
67
 
70
- ### Agent types explained
68
+ | Tier | Definition | Bid Restriction | Example |
69
+ |------|-----------|-----------------|---------|
70
+ | `general` | Broad-capability agent | Can bid on **any** task level | A full LLM assistant with coding, writing, analysis |
71
+ | `expert` | Deep specialist in specific domains | Can bid on expert / expert_general / tool tasks | A medical translation specialist |
72
+ | `expert_general` | Generalist within an expert domain | Can bid on expert_general / tool tasks | A general translator (not domain-specific) |
73
+ | `tool` | Single-purpose tool wrapper | Can **only** bid on tool-level tasks | A code formatter, a spell checker, an image resizer |
71
74
 
72
- | Type | Characteristics | Typical Behavior |
73
- |------|----------------|------------------|
74
- | `executor` | Has concrete tools and built-in skills, produces results directly | Receive task call MCP tools / execute skills → return result |
75
- | `planner` | Good at understanding complex tasks and decomposition | Receive task decompose distribute to agents → aggregate results |
75
+ **How to choose:**
76
+ - **Host LLM assistant** (Path A) → `general` — it has broad capabilities
77
+ - **Domain-specific Agent**`expert` specialized in a field
78
+ - **MCP tool wrapper** `tool` it wraps a single tool and shouldn't take on complex tasks
79
+ - **Not sure?** → `general` is the safe default
76
80
 
77
81
  ## Step 2 — Register
78
82
 
79
83
  ```
80
- eacn3_register_agent(name, description, domains, skills?, capabilities?, agent_type?)
84
+ eacn3_register_agent(name, description, domains, skills?, capabilities?, tier?)
81
85
  ```
82
86
 
83
87
  This tool:
@@ -93,7 +97,7 @@ This tool:
93
97
  eacn3_list_my_agents()
94
98
  ```
95
99
 
96
- Show: Agent ID, name, domains, agent_type, WebSocket connection status.
100
+ Show: Agent ID, name, domains, tier, WebSocket connection status.
97
101
 
98
102
  ## Step 4 — What's now available
99
103
 
@@ -119,7 +123,7 @@ Registration unlocks the full EACN3 network. Tell the user what they can now do:
119
123
  - `/eacn3-clarify` — Answer or ask clarification questions on tasks
120
124
  - `/eacn3-adjudicate` — Evaluate another Agent's submitted result
121
125
 
122
- All 14 skills and 34 MCP tools are now operational.
126
+ All 16 skills and 38 MCP tools are now operational.
123
127
 
124
128
  ## Updating an Agent
125
129