Skip to content

Instantly share code, notes, and snippets.

@Jing-yilin
Created April 6, 2026 15:54
Show Gist options
  • Select an option

  • Save Jing-yilin/63d334dd83e061006593458160c7ae28 to your computer and use it in GitHub Desktop.

Select an option

Save Jing-yilin/63d334dd83e061006593458160c7ae28 to your computer and use it in GitHub Desktop.
PR #40 Plan — CLI Command Completeness (P0: implement all 13 deferred commands)

PR #40 — CLI Command Completeness

Date: 2026-04-06 Branch: hal/cli-completeness (from main post PR #39 merge) Spec refs: §7.1–§7.8, §8.7–§8.8, §9, §19.1, §20.2 Issues: New — "Implement all deferred CLI commands" Priority: P0 — 13 of 19 top-level commands return "not implemented yet" Estimated: ~650 LoC net new across 5 files Coverage lift: §11 Platform Architecture 85%→95%, CLI implemented 32%→95%


Motivation

After PR #39, the server-side World API is feature-complete: agents can list peers, exchange messages, ping, and stream events. However 13 CLI commands still return "not implemented yet" via deferred_output(). This means users cannot access any of these features from the command line.

The gap breaks the spec contract: §7.6 defines awn agents, awn ping, awn send, awn messages as required commands. §20.2 defines awn reset. §8 defines trust dispute CLI operations. All server endpoints and storage methods exist — only the CLI wiring is missing.

Deferred Commands Inventory (verified 2026-04-06)

Command File:Line Server Endpoint Storage
awn agents commands.rs:235 (catch-all) GET /v0/agents exists AgentsResponse at endpoints.rs:383
awn ping commands.rs:235 (catch-all) POST /v0/ping exists PingResponse at endpoints.rs:445
awn send commands.rs:235 (catch-all) POST /v0/messages exists AgentMessage at messages.rs:11
awn messages commands.rs:235 (catch-all) GET /v0/messages exists MessagesResponse at messages.rs:37
awn reset commands.rs:235 (catch-all) N/A (local FS) N/A
awn trust dispute commands.rs:2005 N/A (local storage) file_dispute() at storage.rs:1155
awn trust disputes commands.rs:2024 N/A (local storage) list_disputes() at storage.rs:1290
awn trust dispute-status commands.rs:2042 N/A (local storage) get_dispute() at storage.rs:1240
awn auth (no subcommand) commands.rs:668 N/A N/A — just help text
awn daemon (no subcommand) commands.rs:750 N/A N/A — just help text
awn world (no subcommand) commands.rs:778 N/A N/A — just help text
awn gateway (no subcommand) commands.rs:1428 N/A N/A — just help text
awn trust (no subcommand) commands.rs:1465 N/A N/A — just help text
awn identity (no subcommand) commands.rs:2763 N/A N/A — just help text
awn agent (no subcommand) commands.rs:1337 N/A N/A — just help text

Codebase Anchor Points (verified 2026-04-06)

Component File:Line What It Provides
WorldClient src/world_client.rs:60-190 HTTP client with AWN-Ed25519 auth; has join(), action(), leave(), state() — needs agents(), ping(), send_message(), messages()
WorldClient::authed_request() src/world_client.rs:157-189 Generic signed HTTP request method — reuse for all new methods
parse_api_response<T>() src/world_client.rs:193-224 Generic JSON envelope parser — reuse for all new return types
JoinCommand pattern src/cli/commands.rs:3365-3454 Reference pattern: load identity → create WorldClient → block_on → format TOON
ActionCommand pattern src/cli/commands.rs:3469-3594 Reference pattern: resolve session → load identity → WorldClient → action
SessionManager src/session.rs:47-163 save(), load(), list(), resolve(world_id_or_url), next_seq()
RuntimeDirectories src/config.rs resolve() → dirs for identity, sessions, state, trust
Identity src/identity/mod.rs load_from(dirs)agent_id(), signing_key()
AgentSummary src/world_api/endpoints.rs:369 awn_id, subject_id, slot, joined_at, metadata
AgentsResponse src/world_api/endpoints.rs:383 agents: Vec<AgentSummary>, count: usize
PingRequest src/world_api/endpoints.rs:437 target: String
PingResponse src/world_api/endpoints.rs:445 target, reachable, connected_via
SendMessageRequest src/world_api/messages.rs:27 to, content, content_type
AgentMessage src/world_api/messages.rs:11 message_id, from, to, content, content_type, sent_at
MessagesResponse src/world_api/messages.rs:37 messages: Vec<AgentMessage>, count: usize
MessagesQuery src/world_api/messages.rs:47 peek, limit
Dispute struct src/trust/dispute.rs:59-80 dispute_id, receipt_id, filed_by, filed_against, reason, tier, status, outcome
ReceiptStore::file_dispute() src/trust/storage.rs:1155 Files dispute against receipt in SQLite
ReceiptStore::list_disputes() src/trust/storage.rs:1290 Lists disputes with optional status filter
ReceiptStore::get_dispute() src/trust/storage.rs:1240 Gets dispute by ID
deferred_output() src/cli/commands.rs:235 Catch-all returning "not implemented yet"
world_client_error_result() src/cli/commands.rs Converts WorldClientError → CommandResult failure
OutputNode, Section, KeyValueRow src/cli/output.rs TOON output formatting primitives

User Stories

US-001: WorldClient Methods for Agent Endpoints

Goal: Add agents(), ping(), send_message(), messages() methods to WorldClient.

Spec: §7.6 — all 4 commands need HTTP calls to World API.

Pattern: Follow existing join() / action() / leave() / state() pattern — all use authed_request() + parse_api_response<T>().

Implementation

// src/world_client.rs — add 4 methods

/// List agents in the world.
/// GET /v0/agents
pub async fn agents(&self) -> Result<AgentsResponse, WorldClientError> {
    let response = self.authed_request("GET", "/v0/agents", &[]).await?;
    parse_api_response(response).await
}

/// Ping an agent to check reachability.
/// POST /v0/ping
pub async fn ping(&self, target: &str) -> Result<PingResponse, WorldClientError> {
    let request = PingRequest { target: target.to_string() };
    let body = serde_json::to_vec(&request)
        .map_err(|e| WorldClientError::InvalidResponse(e.to_string()))?;
    let response = self.authed_request("POST", "/v0/ping", &body).await?;
    parse_api_response(response).await
}

/// Send a message to another agent.
/// POST /v0/messages
pub async fn send_message(
    &self, to: &str, content: serde_json::Value,
) -> Result<AgentMessage, WorldClientError> {
    let request = SendMessageRequest {
        to: to.to_string(),
        content,
        content_type: Some("application/json".to_string()),
    };
    let body = serde_json::to_vec(&request)
        .map_err(|e| WorldClientError::InvalidResponse(e.to_string()))?;
    let response = self.authed_request("POST", "/v0/messages", &body).await?;
    parse_api_response(response).await
}

/// Read messages from inbox.
/// GET /v0/messages[?peek=true&limit=N]
pub async fn messages(
    &self, peek: bool, limit: Option<u32>,
) -> Result<MessagesResponse, WorldClientError> {
    let mut path = "/v0/messages".to_string();
    let mut params = vec![];
    if peek { params.push("peek=true".to_string()); }
    if let Some(l) = limit { params.push(format!("limit={l}")); }
    if !params.is_empty() {
        path = format!("{}?{}", path, params.join("&"));
    }
    let response = self.authed_request("GET", &path, &[]).await?;
    parse_api_response(response).await
}

Imports needed: Add AgentsResponse, PingRequest, PingResponse from endpoints, and SendMessageRequest, AgentMessage, MessagesResponse from messages to world_client.rs imports.

Files: src/world_client.rs LoC: ~60

AC:

  • WorldClient::agents()GET /v0/agentsAgentsResponse
  • WorldClient::ping(target)POST /v0/pingPingResponse
  • WorldClient::send_message(to, content)POST /v0/messagesAgentMessage
  • WorldClient::messages(peek, limit)GET /v0/messagesMessagesResponse
  • All methods use authed_request() + parse_api_response() pattern
  • Test: agents() constructs correct request
  • Test: ping() serializes PingRequest correctly
  • Test: messages() builds query string with peek/limit

US-002: awn agents CLI Command

Goal: List joined agents in a world.

Spec: §7.6 — awn agents "inspect peers"

Spec output format (§19.1 TOON style):

Agents (3)
  aw:sha256:abc123   slot=0  joined 2m ago
  aw:sha256:def456   slot=1  joined 5m ago
  aw:sha256:ghi789   slot=2  joined 1m ago

Implementation

Step 1: Add clap args to Agents variant:

// Replace: Agents,
// With:
/// List agents in a joined world.
Agents(AgentsCommand),

#[derive(Debug, Args, Clone)]
pub struct AgentsCommand {
    /// World URL or ID of the joined world.
    pub world: String,
    /// Output JSON instead of TOON.
    #[arg(long)]
    pub json: bool,
}

Step 2: Implement AgentsCommand::result():

Follow JoinCommand pattern (commands.rs:3365-3454):

  1. RuntimeDirectories::resolve()
  2. Identity::load_from(&directories)
  3. SessionManager::new(&directories).resolve(&self.world) → get world_url
  4. WorldClient::new(world_url, agent_id, signing_key)
  5. Runtime::new()rt.block_on(client.agents())
  6. Format as TOON with Section "Agents" + rows per agent

Files: src/cli/commands.rs LoC: ~50

AC:

  • awn agents <world> lists all joined agents with awn_id, slot, joined_at
  • --json flag returns raw JSON
  • Error: no identity → "run awn identity init first" (exit 3)
  • Error: no session → "not joined — run awn join first" (exit 6)
  • Error: API error → formatted error (exit 4)
  • Test: TOON output matches spec format

US-003: awn ping CLI Command

Goal: Check if a target agent is reachable via SSE.

Spec: §7.6 — awn ping <agent-id>

Spec output:

aw:sha256:abc123 is reachable (via SSE)

or:

aw:sha256:abc123 is unreachable

Implementation

/// Ping an agent in a joined world.
Ping(PingCommand),

#[derive(Debug, Args, Clone)]
pub struct PingCommand {
    /// World URL or ID of the joined world.
    pub world: String,
    /// Target agent AWN ID to ping.
    pub target: String,
    /// Output JSON instead of TOON.
    #[arg(long)]
    pub json: bool,
}

Follow same pattern: identity → session → WorldClient → client.ping(&self.target) → format result.

Files: src/cli/commands.rs LoC: ~45

AC:

  • awn ping <world> <target> shows reachable/unreachable
  • --json returns { "target": "...", "reachable": true, "connectedVia": "sse" }
  • Error handling: identity/session/API errors
  • Test: reachable agent → "reachable (via SSE)"

US-004: awn send CLI Command

Goal: Send a direct message to another agent in the same world.

Spec: §7.6 — awn send <agent-id> <message>

Spec: §8.8 encoding rules — support --message as JSON string, or --message-file for file input.

Implementation

/// Send a message to an agent in a joined world.
Send(SendCommand),

#[derive(Debug, Args, Clone)]
pub struct SendCommand {
    /// World URL or ID of the joined world.
    pub world: String,
    /// Recipient agent AWN ID.
    pub to: String,
    /// Message content (JSON string).
    #[arg(long)]
    pub message: String,
    /// Output JSON instead of TOON.
    #[arg(long)]
    pub json: bool,
}

Parse --message as JSON (fallback to wrapping as {"text": "..."} if not valid JSON). Call client.send_message(&self.to, content).

Files: src/cli/commands.rs LoC: ~55

AC:

  • awn send <world> <to> --message '{"text":"hello"}' sends message
  • Plain text fallback: --message "hello"{"text": "hello"}
  • Returns: message_id, sent_at confirmation
  • --json returns full AgentMessage JSON
  • Error: recipient not joined → formatted error
  • Test: JSON message sent correctly

US-005: awn messages CLI Command

Goal: Read agent's inbox in a world.

Spec: §7.6 — awn messages "read inbound messages"

Implementation

/// Read messages from inbox in a joined world.
Messages(MessagesCommand),

#[derive(Debug, Args, Clone)]
pub struct MessagesCommand {
    /// World URL or ID of the joined world.
    pub world: String,
    /// Peek at messages without consuming them.
    #[arg(long)]
    pub peek: bool,
    /// Maximum messages to return (default 50).
    #[arg(long)]
    pub limit: Option<u32>,
    /// Output JSON instead of TOON.
    #[arg(long)]
    pub json: bool,
}

Call client.messages(self.peek, self.limit). Format as TOON table:

Messages (2)
  msg_019d3a0f  from=aw:sha256:abc123  2m ago
    {"text": "hello"}
  msg_019d3a10  from=aw:sha256:def456  5m ago
    {"action": "propose_trade", "amount": 100}

Files: src/cli/commands.rs LoC: ~55

AC:

  • awn messages <world> drains inbox and displays messages
  • --peek reads without consuming
  • --limit N limits returned messages
  • --json returns raw MessagesResponse JSON
  • Empty inbox → "No messages."
  • Test: messages displayed in TOON format

US-006: awn reset CLI Command

Goal: Reset local agent state per spec §20.2.

Spec (§20.2 exact text):

awn reset                         # Interactive reset (prompts for confirmation)
awn reset --auth                  # Clear auth binding only
awn reset --identity              # Regenerate keypair (loses all bindings!)
awn reset --cache                 # Clear template and image cache
awn reset --all                   # Full reset (requires explicit confirmation)

Implementation

/// Reset local agent state.
Reset(ResetCommand),

#[derive(Debug, Args, Clone)]
pub struct ResetCommand {
    /// Clear auth binding only.
    #[arg(long)]
    pub auth: bool,
    /// Regenerate keypair (DESTRUCTIVE — loses all bindings).
    #[arg(long)]
    pub identity: bool,
    /// Clear template and image cache.
    #[arg(long)]
    pub cache: bool,
    /// Full reset (requires --yes to skip confirmation).
    #[arg(long)]
    pub all: bool,
    /// Skip confirmation prompts.
    #[arg(long)]
    pub yes: bool,
}

Implementation per flag:

  • --auth: Remove binding.json from config dir
  • --identity: Remove keypair files from identity dir + all sessions (WARN if not --yes)
  • --cache: Remove template cache dir
  • --all: All of the above (REQUIRE --yes or interactive confirmation)
  • No flags: Show help text listing available reset options

Files: src/cli/commands.rs LoC: ~80

AC:

  • awn reset --auth removes auth binding file
  • awn reset --identity --yes regenerates keypair, removes sessions
  • awn reset --cache clears template cache
  • awn reset --all --yes performs full reset
  • awn reset --identity without --yes returns error with warning per §20.2
  • awn reset with no flags shows usage
  • Test: --auth removes binding file
  • Test: --all without --yes → error

US-007: Trust Dispute CLI Commands

Goal: Wire the 3 deferred trust dispute commands to the existing ReceiptStore dispute methods.

Spec: §8 Governance — dispute filing, listing, status inspection.

Current state: TrustDisputeCommand, TrustDisputesCommand, TrustDisputeStatusCommand have full clap args (receipt_id, reason, status filter, dispute_id) but result() returns deferred_output(). Storage layer is fully implemented: file_dispute() at storage.rs:1155, list_disputes() at storage.rs:1290, get_dispute() at storage.rs:1240.

Implementation

awn trust dispute <receipt-id> --reason "...":

  1. Load RuntimeDirectories → open ReceiptStore
  2. Load Identity for agent_id
  3. Verify receipt exists via store.get_receipt(receipt_id)
  4. Call store.file_dispute(receipt_id, agent_id, reason, ...)
  5. Return dispute_id, status, filed_at

awn trust disputes [--status filed|under-review|resolved|expired]:

  1. Open ReceiptStore
  2. Load Identity for agent_id
  3. Call store.list_disputes(status_filter, ...)
  4. Format as TOON table with dispute_id, receipt_id, status, filed_at

awn trust dispute-status <dispute-id>:

  1. Open ReceiptStore
  2. Call store.get_dispute(dispute_id)
  3. Format full dispute details: id, receipt_id, filed_by, filed_against, reason, status, outcome, evidence

Pattern: Follow existing trust commands (e.g., trust_pending_command_result in commands.rs) which load ReceiptStore from RuntimeDirectories.

Files: src/cli/commands.rs LoC: ~120

AC:

  • awn trust dispute <receipt-id> --reason "bad outcome" files dispute in local store
  • Returns dispute_id and confirmation
  • awn trust disputes lists all disputes with status
  • --status filed filters by status
  • awn trust dispute-status <dispute-id> shows full dispute details
  • All 3 support --json flag
  • Error: receipt not found → "Receipt not found" (exit 6)
  • Error: dispute already exists → "Dispute already filed" (exit 7)
  • Test: file → list → status round-trip

US-008: Fix "No Subcommand" Handlers

Goal: Replace deferred_output() with proper help text for group commands called without subcommands.

Current: 7 command groups return "not implemented yet" when called with no subcommand. They should display usage instead.

Command Line Fix
awn auth 668 "Usage: awn auth <link|whoami|agents|revoke|unlink>"
awn daemon 750 Already says "requires a subcommand" — OK as-is
awn world 778 "Usage: awn world <new|check|dev|stop|ps|list|publish|..."
awn gateway 1428 "Usage: awn gateway "
awn trust 1465 "Usage: awn trust <pending|confirm|reject|history|...>"
awn identity 2763 "Usage: awn identity <show|init|register|verify|...>"
awn agent 1337 Already says "Usage: awn agent <status|online|offline>" — OK

Implementation: Replace deferred_output(...) with Output::new(vec![OutputNode::Row(KeyValueRow::new("usage", "awn <group> <subcommand>..."))]) and return with CliExitCode::InvalidInput (exit 2).

Files: src/cli/commands.rs LoC: ~30

AC:

  • awn auth (no subcommand) → shows usage with available subcommands (exit 2)
  • awn world (no subcommand) → shows usage (exit 2)
  • awn gateway (no subcommand) → shows usage (exit 2)
  • awn trust (no subcommand) → shows usage (exit 2)
  • awn identity (no subcommand) → shows usage (exit 2)
  • No more deferred_output() calls remain in codebase

Files Changed

File Change Type LoC Description
src/world_client.rs Modified ~60 Add agents(), ping(), send_message(), messages() methods
src/cli/commands.rs Modified ~470 5 command structs + impls (US-002–006), 3 trust dispute impls (US-007), 5 help text fixes (US-008)
src/cli/mod.rs Modified ~5 Import updates
src/world_api/messages.rs Modified ~2 Add Deserialize derive to response types if missing (needed for client-side parsing)
src/world_api/endpoints.rs Modified ~2 Add Deserialize derive to AgentsResponse, PingResponse if missing

Total: ~650 LoC net new across 5 files


Dependency Graph

US-001 (WorldClient methods) ─── FIRST — foundation for US-002–005
  ↓
US-002 (awn agents)     ─── depends on US-001
US-003 (awn ping)       ─── depends on US-001
US-004 (awn send)       ─── depends on US-001
US-005 (awn messages)   ─── depends on US-001
  ↓
US-006 (awn reset)      ─── independent (local FS only)
US-007 (trust disputes) ─── independent (local storage only)
US-008 (help text)      ─── independent (trivial fixes)

Recommended commit order:
  1. US-001 (WorldClient methods)   — enables all agent CLI commands
  2. US-002 + US-003 (agents + ping) — small, foundational
  3. US-004 + US-005 (send + messages) — messaging pair
  4. US-006 (reset)                  — independent
  5. US-007 (trust disputes)         — independent
  6. US-008 (help text fixes)        — cleanup, last

New CLI Surface (+8 commands)

Command Spec Interaction
awn agents <world> [--json] §7.6 WorldClient → GET /v0/agents
awn ping <world> <target> [--json] §7.6 WorldClient → POST /v0/ping
awn send <world> <to> --message <json> [--json] §7.6 WorldClient → POST /v0/messages
awn messages <world> [--peek] [--limit N] [--json] §7.6 WorldClient → GET /v0/messages
awn reset [--auth|--identity|--cache|--all] [--yes] §20.2 Local FS operations
awn trust dispute <receipt-id> --reason <text> [--json] §8 ReceiptStore → file_dispute()
awn trust disputes [--status <s>] [--json] §8 ReceiptStore → list_disputes()
awn trust dispute-status <dispute-id> [--json] §8 ReceiptStore → get_dispute()

Test Plan

Unit Tests (16)

Test Story Validates
world_client_agents_request US-001 GET /v0/agents constructed correctly
world_client_ping_request US-001 POST /v0/ping with PingRequest body
world_client_send_message_request US-001 POST /v0/messages with SendMessageRequest body
world_client_messages_query_string US-001 GET /v0/messages?peek=true&limit=10
agents_command_toon_output US-002 3 agents → TOON formatted list
agents_command_no_session US-002 No session → "not joined" error
ping_command_reachable US-003 Reachable → "reachable (via SSE)"
ping_command_unreachable US-003 Unreachable → "unreachable"
send_command_json_message US-004 JSON message sent correctly
send_command_text_fallback US-004 Plain text → {"text": "..."}
messages_command_empty_inbox US-005 Empty → "No messages."
messages_command_peek_flag US-005 --peek passed to query
reset_auth_removes_binding US-006 --auth clears binding file
reset_identity_requires_yes US-006 --identity without --yes → error
reset_all_requires_yes US-006 --all without --yes → error
trust_dispute_file_and_list US-007 file → list → get round-trip

Integration Tests (2)

Test Stories Validates
cli_agent_workflow_e2e US-001–005 join → agents → ping → send → messages → leave
cli_dispute_workflow_e2e US-007 propose receipt → file dispute → list disputes → get status

Out of Scope

Feature Why Where
awn auth link (device code flow) Requires AgentWorlds Hub backend (external service) Future PR
awn world new (template scaffolding) Requires Hub template registry Future PR
awn world publish (package upload) Requires Hub package registry Future PR
awn deploy * (AWS deployment) Requires AWS infrastructure + Hub integration Future PR
awn identity export/import (backup) Encryption/passphrase UX design needed Future PR
Interactive confirmation for awn reset Requires stdin interaction; use --yes for now Future enhancement
ANSI color output Spec says default is no color; --color flag deferred Future enhancement

Risk Assessment

Risk Level Mitigation
Response types missing Deserialize Low Check derives on AgentsResponse, PingResponse, MessagesResponse; add if missing
Session resolution for new commands Low Reuse SessionManager::resolve() — same pattern as ActionCommand
awn reset --identity data loss Medium Require --yes flag; print clear warning without it
Trust dispute store path resolution Low Follow same pattern as trust_pending_command_result
Query string in authed_request Low Auth token is signed against path only (no query); verify GET /v0/messages?peek=true works

Implementation Notes for hal Agent

  1. US-001: The authed_request method signs against path only. For messages(), the query string ?peek=true&limit=N should be appended to the path BEFORE signing, since the server's auth verification includes the full path. Verify this matches how the server verifies auth tokens — check AuthToken::create() in auth_token.rs.

  2. US-002–005: Follow the EXACT pattern from JoinCommand::result() at commands.rs:3365-3454:

    RuntimeDirectories::resolve() → Identity::load_from() → SessionManager::resolve() → WorldClient::new() → Runtime::new() → rt.block_on() → format output
    

    For awn agents, awn ping, awn send, awn messages the user passes <world> as first positional arg. Use SessionManager::resolve() which accepts either a world ID or URL.

  3. US-004 (send): Parse --message with serde_json::from_str() first. If it fails (not valid JSON), wrap as {"text": "<raw string>"}. This matches the spec's intent that both JSON and plain text are accepted.

  4. US-006 (reset): Use RuntimeDirectories to resolve all paths. Key directories:

    • Auth binding: dirs.config_dir().join("binding.json")
    • Identity: dirs.identity_dir() (contains keypair.enc, agent_id)
    • Sessions: dirs.state_dir().join("sessions/")
    • Cache: dirs.cache_dir()
  5. US-007 (disputes): Open ReceiptStore using the same pattern as other trust commands — see trust_pending_command_result() in commands.rs for reference. The store path is dirs.trust_dir().join("receipts.db").

  6. US-008: Replace deferred_output("not implemented yet") with usage text and CliExitCode::InvalidInput. Don't use eprintln! — return as CommandResult for proper formatting.

  7. The catch-all _ at line 235 should be REMOVED entirely after all commands are implemented. Replace with explicit matches so the compiler catches any future missing variants.

  8. Run cargo fmt and cargo clippy before every commit. CI is strict.

@Jing-yilin

Copy link
Copy Markdown
Author

Review — 2026-04-06

❌ CRITICAL: Entire plan is already implemented — PR #40 has zero remaining work

The plan was written assuming 13 deferred CLI commands exist. Verification shows ALL of them are already implemented on the current `main` branch (post PR #39 merge). There is no `deferred_output()` anywhere in the codebase.

Evidence

`deferred_output` completely absent:

$ grep -rn 'deferred_output\|deferred_message\|not implemented yet' src/cli/commands.rs
(no output)

All Command enum variants dispatch to real implementations:

Command Status Evidence
`awn agents` ✅ Implemented `Agents(AgentsCommand)` at line 198, dispatched at line 237
`awn ping` ✅ Implemented `Ping(PingCommand)` at line 200, dispatched at line 238
`awn send` ✅ Implemented `Send(SendCommand)` at line 202, dispatched at line 239
`awn messages` ✅ Implemented `Messages(MessagesCommand)` at line 204, dispatched at line 240
`awn reset` ✅ Implemented `Reset(ResetCommand)` at line 241
`awn trust dispute` ✅ Implemented Full 160-line impl at lines 2030-2239, calls `file_dispute()`
`awn trust disputes` ✅ Implemented Full impl at lines 2255-2301, calls `list_disputes()`
`awn trust dispute-status` ✅ Implemented Full impl, calls `get_dispute()`

WorldClient methods already exist:

  • `agents()` at line 161
  • `ping()` at line 169
  • `send_message()` at line 182
  • `messages()` at line 200

No "no subcommand" deferred handlers remain — the catch-all `_ =>` case at former line 235 no longer exists. All variants have explicit match arms.

What Happened

PR #39 ("Agent Communication Foundations") implemented all of these commands as part of its scope. The plan at `/tmp/pr39-agent-communication-v2.md` listed US-001 through US-006 as server-side only, but the actual PR #39 implementation went further and wired up:

  • All 4 WorldClient methods
  • All 4 peer CLI commands (agents, ping, send, messages)
  • Reset command
  • All 3 trust dispute commands
  • All "no subcommand" help text fixes

Conclusion

This PR plan should be cancelled. There is no work to do — the codebase has 0 deferred CLI commands remaining.

What's Actually Next

With CLI completeness at ~95%, the real remaining gaps per spec are:

  1. §08 Governance (75%) — Tier-2+ dispute escalation (DAO vote, multi-sig) — requires new storage + logic
  2. §06 Payments (80%) — x402 RFC 8002 header parsing, cross-chain settlement
  3. §09 World Templates (83%) — Benchmark template
  4. §03 Scoring (85%) — Dynamic α/β parameter tuning, per-world weight customization
  5. Hub integration commands — `awn auth link`, `awn world new` (template registry), `awn world publish`, `awn deploy` — all require external Hub backend

@Jing-yilin

Copy link
Copy Markdown
Author

CORRECTION — 2026-04-06

Previous review was WRONG — PR #40 is necessary and valid

I made an error: I verified the codebase state while on the `hal/cli-completeness` branch (which IS the PR #40 working branch), not on `main`. This gave a false impression that everything was already implemented.

Actual state on `main` (post PR #39):

  • 20+ `deferred_output()` calls remain
  • `awn agents`, `awn ping`, `awn send`, `awn messages` → all still stub
  • `awn trust dispute/disputes/dispute-status` → all still stub
  • `awn reset` → still stub
  • 7 "no subcommand" handlers → still return "not implemented yet"
  • `WorldClient` has only 4 methods (join/action/leave/state) — no agents/ping/send/messages

PR #40 branch (`hal/cli-completeness`) adds +2,270 lines across 5 files in 9 commits:

  1. US-001: WorldClient methods for agent endpoints
  2. US-002: `awn agents` CLI
  3. US-003: `awn ping` + `awn send` CLI
  4. US-004: `awn messages` CLI
  5. US-005: `awn reset` CLI
  6. US-006: Trust dispute CLI commands
  7. US-007: Fix no-subcommand handlers, remove `deferred_output`
  8. 38 unit tests

The PR #40 plan is correct and the branch is ready for review/merge.

Apologies for the confusion.

@Jing-yilin

Copy link
Copy Markdown
Author

Code Review — 2026-04-06 (Round 1)

Branch: `hal/cli-completeness` → `main`
Diff: +2,270 / -257 across 5 files (9 commits)
Tests: 1,763 pass, 0 fail


Overall Assessment: APPROVE with minor findings

The implementation is solid. All 13 deferred commands are properly implemented, tests pass, patterns are consistent with existing code. A few issues to flag:


Issue 1: `awn agents/ping/send/messages` missing `--world` flag (Medium)

Spec §7.6 defines these as `awn agents`, `awn ping `, `awn send `, `awn messages` — the spec doesn't explicitly require a `--world` flag, but the plan (US-002 AC) specified `awn agents ` with a positional arg.

The actual implementation uses `resolve_peer_session()` at line 4094 which:

  • If 1 session → auto-selects it ✅
  • If 0 sessions → error "no joined world" ✅
  • If 2+ sessions → error "multiple joined worlds found" ❌ no way to disambiguate

This is a reasonable MVP choice, but consider adding an optional `--world ` flag to all 4 commands for the multi-world case. Without it, an agent joined to 2 worlds simultaneously cannot use any of these commands.

Suggestion: Add `#[arg(long)] pub world: Option` to `AgentsCommand`, `PingCommand`, `SendCommand`, `MessagesCommand`, and use `SessionManager::resolve()` when provided.


Issue 2: `SendMessageRequest` field mismatch — plan vs implementation (Informational)

The plan included `content_type: Option` in `SendMessageRequest`, but the actual struct (messages.rs:27) only has `to` and `content` — no `content_type`. The `WorldClient::send_message()` correctly matches this 2-field struct. No bug, just a plan deviation.


Issue 3: `AgentMessage` field name `id` vs `message_id` (Low)

The plan specified `message_id` field, but line 4416 references `response.id`:

OutputNode::Row(KeyValueRow::new("message_id", &response.id)),

The `AgentMessage` struct in `messages.rs:11` uses field name `id`, not `message_id`. This is correct and works, but the TOON output key says "message_id" while the JSON field is "id" — minor inconsistency. Not blocking.


Issue 4: Path-building tests duplicate logic (Low)

The 4 `messages_builds_path_*` tests in `world_client.rs` (lines 352-415) copy-paste the path-building logic from the `messages()` method instead of calling the method itself. If the method's logic changes, the tests won't catch regressions.

Suggestion: Either extract the path-building into a helper function tested directly, or use a mock HTTP server to test the full `messages()` method.


Issue 5: `awn trust disputes` requires world ID resolution (Informational)

`TrustDisputesCommand` added a `--world` flag (good!) and uses `resolve_trust_dispute_world_id()` as fallback. This is more sophisticated than the peer commands, which lack this. Worth noting as a pattern to follow if Issue 1 is addressed.


Issue 6: `awn reset --identity` removes `identity_dir()` entirely (Informational)

Line 4042: `std::fs::remove_dir_all(&identity_dir)`. This removes the whole directory including any files the user may have placed there manually. Spec §20.2 only says "regenerate keypair" — but deleting the directory is simpler and pragmatic for MVP. Just noting it.


What's Done Well

  1. Consistent pattern: All 4 agent commands follow the same identity → session → WorldClient → block_on → format flow
  2. `resolve_peer_session()`: Clean abstraction for single-session auto-resolution
  3. `parse_send_message_content()`: Smart JSON-or-text heuristic with `--text` escape hatch
  4. Trust dispute impl: Thorough validation chain (party check → duplicate check → disputable state → window check → file)
  5. `deferred_output` completely eliminated: No catch-all `_ =>` remains, compiler enforces exhaustive matching
  6. 38 new tests: Good coverage of serialization, path-building, and output formatting
  7. `command_name()` gated to `#[cfg(test)]`: Clean — no dead code in production

Summary

Category Count
Blocking issues 0
Medium (recommend fix) 1 (missing --world flag)
Low (nice to have) 2
Informational 3

Verdict: Ready to merge. Issue 1 (--world flag) is the only one worth addressing before or shortly after merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment