feat(term-challenge): add ratatui CLI, agent storage module, and documentation#4
feat(term-challenge): add ratatui CLI, agent storage module, and documentation#4
Conversation
- Create agent_storage module with store/retrieve functions for agent code, hashes, and evaluation logs using host storage - Add AgentLogEntry and AgentLogs types to types.rs - Add 1MB package_zip size validation in validate() - Store agent code, hash, and logs during evaluate() - Add GET /agent/:hotkey/code and /agent/:hotkey/logs routes
Add a new native binary crate 'cli/' with a ratatui-based terminal UI for monitoring the term-challenge. Features include: - Four tabs: Leaderboard, Evaluation, Submission, Network - JSON-RPC 2.0 client for fetching data from chain endpoint - Auto-refresh every 10 seconds with manual refresh via 'r' key - Tab/Shift+Tab navigation, Up/Down scrolling, 'q' to quit - Status bar showing epoch, phase, block height, validators - Graceful error handling with TUI error display - CLI args via clap: --rpc-url, --hotkey, --challenge-id, --tab The workspace uses default-members to prevent the CLI from being built when targeting wasm32-unknown-unknown.
Remove unused dataset consensus functions from dataset.rs that were not yet integrated with the P2P consensus system. The active dataset is currently managed via ChallengeParams passed to evaluate(), making these functions dead code. The module now contains documentation notes for future reimplementation. Remove unused get_dataset_history() from tasks.rs which also contained an .unwrap_or_default() pattern on deserialization. Add doc comments and #[allow(dead_code)] annotations to agent_storage retrieval functions (get_agent_code, get_agent_logs), route definitions (get_route_definitions), and scoring utilities (to_weight, apply_decay) that are defined for future platform-v2 integration but not yet called internally. Add module-level documentation to routes.rs explaining the challenge_call RPC integration path.
📝 WalkthroughWalkthroughThis pull request extends the term-challenge workspace by adding a new native CLI application with terminal UI and introducing on-chain storage for agent code, hashes, and execution logs. The WASM evaluation flow integrates with the new agent storage module, while dataset consensus functionality is removed. Documentation is significantly expanded to describe the architecture and data flows. Changes
Sequence DiagramssequenceDiagram
participant Agent as Agent Submission
participant Eval as WASM Evaluate
participant Storage as Agent Storage
participant OnChain as On-Chain Storage
Agent->>Eval: submit(code_zip, params)
Eval->>Eval: extract metadata (hotkey, epoch, hash)
Eval->>Eval: execute tasks & collect results
Eval->>Eval: build AgentLogs with previews
Eval->>Storage: store_agent_code(hotkey, epoch, zip)
Storage->>OnChain: persist code
Eval->>Storage: store_agent_hash(hotkey, epoch, hash)
Storage->>OnChain: persist hash
Eval->>Storage: store_agent_logs(hotkey, epoch, logs)
Storage->>OnChain: persist execution log
Eval->>Agent: return result
sequenceDiagram
participant User as User (CLI)
participant CLI as CLI App
participant RPC as RPC Client
participant Blockchain as Blockchain/RPC
User->>CLI: refresh (manual or periodic)
CLI->>RPC: fetch_system_health()
RPC->>Blockchain: rpc_call(system_health)
Blockchain-->>RPC: health data
RPC-->>CLI: NetworkStatus updated
CLI->>RPC: fetch_leaderboard(challenge_id)
RPC->>Blockchain: rpc_call(leaderboard)
Blockchain-->>RPC: leaderboard rows
RPC-->>CLI: LeaderboardRow vec
CLI->>RPC: fetch_evaluation_progress(hotkey)
RPC->>Blockchain: rpc_call(eval_progress)
Blockchain-->>RPC: eval tasks
RPC-->>CLI: EvalTaskRow vec
CLI->>CLI: render UI with tabs
CLI-->>User: display tabbed interface
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
wasm/src/scoring.rs (1)
82-102:⚠️ Potential issue | 🟠 MajorRemove
apply_decayfunction — it is unused dead code.The
#[allow(dead_code)]annotation onapply_decayviolates the coding guideline: "Do NOT add#[allow(dead_code)]broadly. Fix unused code or remove it." Verification confirmsapply_decayis not called anywhere in the codebase. Since it is documented as "reserved for future use," it should be removed now and reintroduced when decay mechanics are actually integrated.Proposed fix
-/// Apply decay to weight based on hours since top score. -/// -/// Note: This function is reserved for future use when decay mechanics are -/// integrated with the scoring system via challenge configuration. -#[allow(dead_code)] -pub fn apply_decay(weight: f64, hours_since_top: f64, params: &DecayParams) -> f64 { - let grace = params.grace_period_hours as f64; - if hours_since_top <= grace { - return weight; - } - - let elapsed = hours_since_top - grace; - let half_life = params.half_life_hours as f64; - if half_life <= 0.0 { - return params.min_multiplier; - } - - let multiplier = 0.5f64.powf(elapsed / half_life); - let clamped = multiplier.max(params.min_multiplier); - weight * clamped -}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@wasm/src/scoring.rs` around lines 82 - 102, Remove the unused apply_decay function and its accompanying doc comment and #[allow(dead_code)] attribute from the file (the function signature pub fn apply_decay(weight: f64, hours_since_top: f64, params: &DecayParams) -> f64 and its body), and also remove any related unused imports or references to DecayParams in this module if they become unused after deletion; ensure no other code references apply_decay before committing.AGENTS.md (1)
9-34:⚠️ Potential issue | 🟡 MinorAdd a language specifier to the fenced code block to fix the MD040 linting warning.
🛠️ Proposed fix
-``` +```text term-challenge/🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@AGENTS.md` around lines 9 - 34, The fenced code block that shows the project tree starting with "term-challenge/" in AGENTS.md lacks a language specifier; add a language tag (e.g., ```text) to the opening fence for that code block so the MD040 lint warning is resolved and keep the closing ``` intact.
🧹 Nitpick comments (10)
wasm/src/dataset.rs (1)
1-10: Consider removing this placeholder file.This file contains only comments with no functional code. Keeping empty placeholder modules adds noise. The intent can be documented in a tracking issue or
AGENTS.mdinstead, and the module can be reintroduced when P2P consensus is actually implemented.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@wasm/src/dataset.rs` around lines 1 - 10, This module (wasm/src/dataset.rs) contains only explanatory comments and should be removed to reduce noise; either delete the file from the repo or replace it with a single-line module comment that references a tracking issue or AGENTS.md for future P2P dataset consensus work (mentioning the intended features: host_random_seed selection, host_storage_set validator storage, >50% consensus, and dataset serialization/hash verification) so the intent is preserved without an empty placeholder.cli/src/rpc.rs (1)
92-99: No HTTP timeout configured — CLI may hang on unresponsive RPC.
reqwest::Client::new()uses no timeout by default. If the RPC endpoint is down or slow, everycallwill block the refresh loop indefinitely. Configure a connect + request timeout.Proposed fix
pub fn new(url: &str) -> Self { Self { url: url.to_string(), - client: reqwest::Client::new(), + client: reqwest::Client::builder() + .connect_timeout(std::time::Duration::from_secs(5)) + .timeout(std::time::Duration::from_secs(15)) + .build() + .unwrap_or_default(), request_id: AtomicU64::new(1), } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cli/src/rpc.rs` around lines 92 - 99, RpcClient::new currently constructs a reqwest client with Client::new() which has no timeouts; update RpcClient::new to build a reqwest::Client with sensible connect and overall request timeouts (e.g. via reqwest::Client::builder().connect_timeout(Duration::from_secs(...)).timeout(Duration::from_secs(...)).build()), ensuring you import std::time::Duration; keep the AtomicU64 initialization and other fields unchanged so RpcClient::new still returns Self with url, client, and request_id.cli/src/ui.rs (2)
247-309:draw_networkduplicates theParagraph+Blocksetup across both branches.The only difference between the
SomeandNonebranches is the optional "Challenge" line. Consider building lines first, then conditionally appending, and rendering once.Proposed fix
fn draw_network(frame: &mut Frame, area: Rect, app: &App) { let ns = &app.network_status; let connected_style = if ns.connected { Style::default().fg(Color::Green) } else { Style::default().fg(Color::Red) }; let connected_text = if ns.connected { "Connected" } else { "Disconnected" }; - let lines = vec![ + let mut lines = vec![ Line::from(vec![ Span::styled("Status: ", Style::default().fg(Color::Yellow).bold()), Span::styled(connected_text, connected_style), ]), // ... remaining lines ... ]; if let Some(cid) = &app.challenge_id { - let mut all_lines = lines; - all_lines.push(Line::from(vec![ + lines.push(Line::from(vec![ Span::styled("Challenge: ", Style::default().fg(Color::Yellow).bold()), Span::raw(cid.clone()), ])); - let paragraph = Paragraph::new(all_lines).block( - Block::default() - .borders(Borders::ALL) - .title("Network Status"), - ); - frame.render_widget(paragraph, area); - } else { - let paragraph = Paragraph::new(lines).block( - Block::default() - .borders(Borders::ALL) - .title("Network Status"), - ); - frame.render_widget(paragraph, area); } + + let paragraph = Paragraph::new(lines).block( + Block::default() + .borders(Borders::ALL) + .title("Network Status"), + ); + frame.render_widget(paragraph, area); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cli/src/ui.rs` around lines 247 - 309, In draw_network, avoid duplicating Paragraph/Block rendering by building the base `lines` vector then conditionally appending the challenge line when `app.challenge_id` is Some (use the existing `cid`), and finally create a single `Paragraph::new(...)` with the `Block::default().borders(Borders::ALL).title("Network Status")` and call `frame.render_widget` once; reference symbols: function draw_network, variable lines, optional app.challenge_id (cid), Paragraph, Block, frame.render_widget.
348-354:truncate_hotkeyuses byte slicing which panics on multi-byte UTF-8 boundaries.
&hotkey[..max_len]slices by byte index. Ifhotkeyever contains non-ASCII characters, this will panic. While SS58 hotkeys are ASCII in practice, this is fragile. Usechars()for safe truncation.Proposed fix
fn truncate_hotkey(hotkey: &str, max_len: usize) -> String { - if hotkey.len() > max_len { - format!("{}...", &hotkey[..max_len]) + if hotkey.chars().count() > max_len { + let truncated: String = hotkey.chars().take(max_len).collect(); + format!("{truncated}...") } else { hotkey.to_string() } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cli/src/ui.rs` around lines 348 - 354, The function truncate_hotkey uses byte slicing (&hotkey[..max_len]) which will panic on UTF-8 multi-byte boundaries; update truncate_hotkey to operate on Unicode scalar values instead: count characters with hotkey.chars(), if the character count is greater than max_len build a truncated string with hotkey.chars().take(max_len).collect::<String>() and append "..." otherwise return hotkey.to_string(); ensure you use the function name truncate_hotkey and the hotkey/max_len parameters so the change is localized and preserves existing behavior.cli/src/app.rs (3)
176-184: Evaluation progress errors are silently swallowed atdebuglevel.Leaderboard fetch failures are displayed to the user via
error_message, but evaluation progress failures are only logged withtracing::debug!. For a monitoring TUI, this inconsistency may confuse users who see stale or empty evaluation data without explanation. Consider settingerror_messagehere too, or at least usingtracing::warn!.Proposed fix
Err(e) => { - tracing::debug!("Evaluation progress: {e}"); + tracing::warn!("Evaluation progress: {e}"); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cli/src/app.rs` around lines 176 - 184, The error branch in the evaluation progress fetch silently logs at debug level; update the Err(e) handling inside the match on rpc.fetch_evaluation_progress(&hotkey) so it both surfaces the error to the user (e.g., set self.error_message = Some(format!(...)) or call the existing error display helper) and logs it at warn/error level (use tracing::warn! or tracing::error! instead of tracing::debug!); modify the block that currently references self.evaluation_progress and tracing::debug! so failures update user-facing state and produce a higher-severity log.
144-187: Error messages can silently overwrite each other duringrefresh.If the challenge list fetch (Line 161) sets
error_messageand the leaderboard fetch (Line 171) also fails, the leaderboard error overwrites the challenge error. Consider accumulating errors or showing the most recent one with a count indicator.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cli/src/app.rs` around lines 144 - 187, The refresh method currently assigns to self.error_message multiple times so later failures overwrite earlier ones; update refresh (and the struct) to accumulate errors instead of replacing them: add a persistent container (e.g., Vec<String> errors or an error_count + last_error string) on the same struct that holds error_message, then change each failure branch that sets self.error_message (the refresh_network call, rpc.fetch_challenge_list, rpc.fetch_leaderboard, rpc.fetch_evaluation_progress) to push/append the new error text (or increment count and update last_error) rather than assign, and update any UI/consumers to display the accumulated summary or "N errors: <last error>" accordingly. Ensure refresh_network and the RPC failure match arms use the new accumulation API so no error can silently overwrite a previous one.
140-142:scroll_downhas no upper-bound check — user can scroll past all data.
saturating_add(1)increases the offset without limit. When the offset exceeds the number of items, the visible table will be empty. Consider clamping against the active tab's item count.Proposed fix
pub fn scroll_down(&mut self) { - self.scroll_offset = self.scroll_offset.saturating_add(1); + let max = match self.tab { + Tab::Leaderboard => self.leaderboard.len(), + Tab::Evaluation => self.evaluation_progress.len(), + _ => 0, + }; + if self.scroll_offset < max.saturating_sub(1) { + self.scroll_offset = self.scroll_offset.saturating_add(1); + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cli/src/app.rs` around lines 140 - 142, scroll_down currently increments self.scroll_offset with saturating_add(1) and can move past available items; change it to clamp against the active tab's item count by computing the current item_count (e.g., via self.tabs[self.active_tab].items.len() or a helper like active_tab_item_count()), handle the empty-case by setting scroll_offset to 0 when item_count == 0, otherwise compute max_offset = item_count.saturating_sub(1) and set self.scroll_offset = (self.scroll_offset.saturating_add(1)).min(max_offset) so the offset never exceeds the last valid index.wasm/src/lib.rs (1)
256-258: Replace magic number withagent_storage::MAX_AGENT_PACKAGE_SIZEto keep the limit in sync.The literal
1_048_576is the same value aspub const MAX_AGENT_PACKAGE_SIZEdefined inagent_storage.rs. If the constant is ever updated, this check invalidate()won't follow.🛠️ Proposed fix
- if submission.package_zip.len() > 1_048_576 { + if submission.package_zip.len() > agent_storage::MAX_AGENT_PACKAGE_SIZE {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@wasm/src/lib.rs` around lines 256 - 258, In validate() the check using the magic number 1_048_576 should use the shared constant to stay in sync; replace the literal in the condition that reads submission.package_zip.len() > 1_048_576 with agent_storage::MAX_AGENT_PACKAGE_SIZE (or compare against that constant) so the package size limit uses agent_storage::MAX_AGENT_PACKAGE_SIZE instead of a hard-coded value.wasm/src/types.rs (1)
108-115:total_size_bytessemantics are misleading — consider renaming or clarifying.In
lib.rsthis field is populated by summing onlyoutput_preview.len()values (nottask_id, scores,error,miner_hotkey, etc.), so it does not represent the actual serialised size ofAgentLogs. Rename to something liketotal_preview_bytesor add a doc comment clarifying what it counts, to avoid confusing consumers (e.g., the CLI).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@wasm/src/types.rs` around lines 108 - 115, The AgentLogs struct's field total_size_bytes is misleading because lib.rs populates it by summing only each entry's output_preview.len() rather than the serialized size of AgentLogs; update this by either renaming the field to total_preview_bytes (and update all references to AgentLogs::total_size_bytes in lib.rs and other modules to the new name) or add a doc comment on AgentLogs::total_size_bytes clarifying it strictly represents the sum of AgentLogEntry.output_preview lengths (not full serialized size), and ensure the population site in lib.rs that sums output_preview.len() is left consistent with the chosen name/comment.wasm/src/agent_storage.rs (1)
59-65:get_agent_logsdeserializes with unbounded default options — consider adding a size limit for consistency.The rest of the codebase (
lib.rs) usesbincode_options_*()builders with explicit.with_limit(...)for all deserialization.bincode::deserialize(&data)here uses the default unbounded decoder. Because the data was already bounded byMAX_LOG_SIZEat storage time the practical risk is low, but the inconsistency could catch a future reader off-guard.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@wasm/src/agent_storage.rs` around lines 59 - 65, The get_agent_logs function currently calls bincode::deserialize(&data) with unbounded defaults; replace that call with the project's bincode options builder used elsewhere (e.g., the bincode_options_*() helper) and call .with_limit(...) using the same MAX_LOG_SIZE used when storing logs so deserialization is bounded; update the final expression to use something like bincode_options_*().with_limit(MAX_LOG_SIZE).deserialize(&data).ok() to deserialize AgentLogs consistently (function: get_agent_logs, types: AgentLogs, helper: make_key).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@AGENTS.md`:
- Around line 92-95: The keyboard docs list Esc and j/k but the input handling
in cli/src/main.rs only matches KeyCode::Char('q'), KeyCode::Up and
KeyCode::Down; update the key handler in main.rs (the match/handler that
currently checks KeyCode::Char('q'), KeyCode::Up, KeyCode::Down) to also handle
KeyCode::Esc as quit (same action as 'q') and KeyCode::Char('j') /
KeyCode::Char('k') mapping to the same actions as KeyCode::Down and KeyCode::Up
respectively so the AGENTS.md shortcuts are actually implemented.
In `@cli/src/main.rs`:
- Around line 47-52: The current flow calls run(&mut terminal, cli).await
producing result and then calls ratatui::try_restore()? which may propagate and
hide the original run() error; change this so that the restore result is ignored
or handled non-fatally (e.g., assign to let _ = ratatui::try_restore(); or match
and log the restore error) and then return the original result variable so any
Err from run() is always surfaced; update the code around the run/try_restore
calls (references: run, ratatui::try_restore, result, terminal, cli) to ensure
try_restore failure does not overwrite result.
- Around line 76-95: The match on key.code is missing handlers for KeyCode::Esc
and the vim-style keys KeyCode::Char('j') and KeyCode::Char('k') referenced in
AGENTS.md; update the match in the block handling input (the match key.code) to
add KeyCode::Esc => { app.should_quit = true; }, KeyCode::Char('j') =>
app.scroll_down(), and KeyCode::Char('k') => app.scroll_up() so they map to the
existing app.should_quit, app.scroll_down, and app.scroll_up behaviors.
In `@README.md`:
- Around line 143-144: The README entry for dataset.rs is inaccurate because
dataset.rs currently only contains placeholder comments; update the README.md
description for "dataset.rs" to indicate its placeholder status (e.g., change
"Dataset selection consensus" to "Dataset selection consensus (placeholder)") or
remove the dataset.rs line from the tree; locate the README.md listing that
references dataset.rs and edit the descriptive text accordingly so it matches
the current state of the dataset.rs file.
- Line 109: The README still advertises "P2P Dataset Consensus" even though the
related functions were removed from wasm/src/dataset.rs
(select_random_task_indices, store_my_selection, build_consensus_dataset,
create_dataset_selection); update the README to either remove this bullet or
mark it as planned/placeholder (e.g., "P2P Dataset Consensus (planned)"), and
ensure the copy matches current code state so readers aren't misled about
existing functionality.
In `@wasm/src/agent_storage.rs`:
- Around line 44-66: Remove the unnecessary #[allow(dead_code)] attributes from
the retrieval functions get_agent_code and get_agent_logs; these functions are
public and documented so the dead-code lint is not applicable, so simply delete
the #[allow(dead_code)] lines above each function (keeping the doc comments and
function bodies unchanged).
- Around line 68-73: The truncate_output function currently slices the &str with
output[..max_len] which can panic if max_len falls mid-UTF-8 character; update
truncate_output to compute a safe byte index <= max_len that is on a character
boundary (use str::is_char_boundary or iterate char_indices to find the last
valid boundary <= max_len) and then slice using that safe index (e.g.,
&output[..safe_len]) before converting to String; keep the original fast-path
when output.len() <= max_len and ensure the function returns a valid UTF-8
String without panicking.
In `@wasm/src/routes.rs`:
- Around line 13-18: Remove the unnecessary #[allow(dead_code)] attribute on the
get_route_definitions function: delete that attribute from the function
definition (leave the pub fn get_route_definitions(...) and its doc comment
intact) so the crate follows the rule against broad #[allow(dead_code)] usage;
if the function really is unused and you intend to keep it, consider marking it
pub(crate) or wiring up its registration, but do not add #[allow(dead_code)].
---
Outside diff comments:
In `@AGENTS.md`:
- Around line 9-34: The fenced code block that shows the project tree starting
with "term-challenge/" in AGENTS.md lacks a language specifier; add a language
tag (e.g., ```text) to the opening fence for that code block so the MD040 lint
warning is resolved and keep the closing ``` intact.
In `@wasm/src/scoring.rs`:
- Around line 82-102: Remove the unused apply_decay function and its
accompanying doc comment and #[allow(dead_code)] attribute from the file (the
function signature pub fn apply_decay(weight: f64, hours_since_top: f64, params:
&DecayParams) -> f64 and its body), and also remove any related unused imports
or references to DecayParams in this module if they become unused after
deletion; ensure no other code references apply_decay before committing.
---
Nitpick comments:
In `@cli/src/app.rs`:
- Around line 176-184: The error branch in the evaluation progress fetch
silently logs at debug level; update the Err(e) handling inside the match on
rpc.fetch_evaluation_progress(&hotkey) so it both surfaces the error to the user
(e.g., set self.error_message = Some(format!(...)) or call the existing error
display helper) and logs it at warn/error level (use tracing::warn! or
tracing::error! instead of tracing::debug!); modify the block that currently
references self.evaluation_progress and tracing::debug! so failures update
user-facing state and produce a higher-severity log.
- Around line 144-187: The refresh method currently assigns to
self.error_message multiple times so later failures overwrite earlier ones;
update refresh (and the struct) to accumulate errors instead of replacing them:
add a persistent container (e.g., Vec<String> errors or an error_count +
last_error string) on the same struct that holds error_message, then change each
failure branch that sets self.error_message (the refresh_network call,
rpc.fetch_challenge_list, rpc.fetch_leaderboard, rpc.fetch_evaluation_progress)
to push/append the new error text (or increment count and update last_error)
rather than assign, and update any UI/consumers to display the accumulated
summary or "N errors: <last error>" accordingly. Ensure refresh_network and the
RPC failure match arms use the new accumulation API so no error can silently
overwrite a previous one.
- Around line 140-142: scroll_down currently increments self.scroll_offset with
saturating_add(1) and can move past available items; change it to clamp against
the active tab's item count by computing the current item_count (e.g., via
self.tabs[self.active_tab].items.len() or a helper like
active_tab_item_count()), handle the empty-case by setting scroll_offset to 0
when item_count == 0, otherwise compute max_offset =
item_count.saturating_sub(1) and set self.scroll_offset =
(self.scroll_offset.saturating_add(1)).min(max_offset) so the offset never
exceeds the last valid index.
In `@cli/src/rpc.rs`:
- Around line 92-99: RpcClient::new currently constructs a reqwest client with
Client::new() which has no timeouts; update RpcClient::new to build a
reqwest::Client with sensible connect and overall request timeouts (e.g. via
reqwest::Client::builder().connect_timeout(Duration::from_secs(...)).timeout(Duration::from_secs(...)).build()),
ensuring you import std::time::Duration; keep the AtomicU64 initialization and
other fields unchanged so RpcClient::new still returns Self with url, client,
and request_id.
In `@cli/src/ui.rs`:
- Around line 247-309: In draw_network, avoid duplicating Paragraph/Block
rendering by building the base `lines` vector then conditionally appending the
challenge line when `app.challenge_id` is Some (use the existing `cid`), and
finally create a single `Paragraph::new(...)` with the
`Block::default().borders(Borders::ALL).title("Network Status")` and call
`frame.render_widget` once; reference symbols: function draw_network, variable
lines, optional app.challenge_id (cid), Paragraph, Block, frame.render_widget.
- Around line 348-354: The function truncate_hotkey uses byte slicing
(&hotkey[..max_len]) which will panic on UTF-8 multi-byte boundaries; update
truncate_hotkey to operate on Unicode scalar values instead: count characters
with hotkey.chars(), if the character count is greater than max_len build a
truncated string with hotkey.chars().take(max_len).collect::<String>() and
append "..." otherwise return hotkey.to_string(); ensure you use the function
name truncate_hotkey and the hotkey/max_len parameters so the change is
localized and preserves existing behavior.
In `@wasm/src/agent_storage.rs`:
- Around line 59-65: The get_agent_logs function currently calls
bincode::deserialize(&data) with unbounded defaults; replace that call with the
project's bincode options builder used elsewhere (e.g., the bincode_options_*()
helper) and call .with_limit(...) using the same MAX_LOG_SIZE used when storing
logs so deserialization is bounded; update the final expression to use something
like bincode_options_*().with_limit(MAX_LOG_SIZE).deserialize(&data).ok() to
deserialize AgentLogs consistently (function: get_agent_logs, types: AgentLogs,
helper: make_key).
In `@wasm/src/dataset.rs`:
- Around line 1-10: This module (wasm/src/dataset.rs) contains only explanatory
comments and should be removed to reduce noise; either delete the file from the
repo or replace it with a single-line module comment that references a tracking
issue or AGENTS.md for future P2P dataset consensus work (mentioning the
intended features: host_random_seed selection, host_storage_set validator
storage, >50% consensus, and dataset serialization/hash verification) so the
intent is preserved without an empty placeholder.
In `@wasm/src/lib.rs`:
- Around line 256-258: In validate() the check using the magic number 1_048_576
should use the shared constant to stay in sync; replace the literal in the
condition that reads submission.package_zip.len() > 1_048_576 with
agent_storage::MAX_AGENT_PACKAGE_SIZE (or compare against that constant) so the
package size limit uses agent_storage::MAX_AGENT_PACKAGE_SIZE instead of a
hard-coded value.
In `@wasm/src/types.rs`:
- Around line 108-115: The AgentLogs struct's field total_size_bytes is
misleading because lib.rs populates it by summing only each entry's
output_preview.len() rather than the serialized size of AgentLogs; update this
by either renaming the field to total_preview_bytes (and update all references
to AgentLogs::total_size_bytes in lib.rs and other modules to the new name) or
add a doc comment on AgentLogs::total_size_bytes clarifying it strictly
represents the sum of AgentLogEntry.output_preview lengths (not full serialized
size), and ensure the population site in lib.rs that sums output_preview.len()
is left consistent with the chosen name/comment.
| | `Tab` / `Shift+Tab` | Switch between tabs | | ||
| | `↑` / `↓` / `j` / `k` | Navigate rows | | ||
| | `r` | Refresh data | | ||
| | `q` / `Esc` | Quit | |
There was a problem hiding this comment.
Keyboard shortcut table documents Esc and j/k that are not implemented.
The table lists q / Esc for quit and ↑ / ↓ / j / k for navigation, but cli/src/main.rs only handles KeyCode::Char('q'), KeyCode::Up, and KeyCode::Down. Either add the handlers in main.rs (suggested fix in the main.rs review comment) or remove the undocumented shortcuts here.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@AGENTS.md` around lines 92 - 95, The keyboard docs list Esc and j/k but the
input handling in cli/src/main.rs only matches KeyCode::Char('q'), KeyCode::Up
and KeyCode::Down; update the key handler in main.rs (the match/handler that
currently checks KeyCode::Char('q'), KeyCode::Up, KeyCode::Down) to also handle
KeyCode::Esc as quit (same action as 'q') and KeyCode::Char('j') /
KeyCode::Char('k') mapping to the same actions as KeyCode::Down and KeyCode::Up
respectively so the AGENTS.md shortcuts are actually implemented.
|
|
||
| let result = run(&mut terminal, cli).await; | ||
|
|
||
| ratatui::try_restore()?; | ||
|
|
||
| result |
There was a problem hiding this comment.
Original run() error is silently dropped if try_restore() also fails.
If run() returns an Err and try_restore() subsequently also errors, the ? on try_restore() propagates the restore error while discarding the root cause from run(). Consider ignoring the restore result so the run() error is always surfaced:
🛠️ Proposed fix
let result = run(&mut terminal, cli).await;
-ratatui::try_restore()?;
+let _ = ratatui::try_restore();
result📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| let result = run(&mut terminal, cli).await; | |
| ratatui::try_restore()?; | |
| result | |
| let result = run(&mut terminal, cli).await; | |
| let _ = ratatui::try_restore(); | |
| result |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@cli/src/main.rs` around lines 47 - 52, The current flow calls run(&mut
terminal, cli).await producing result and then calls ratatui::try_restore()?
which may propagate and hide the original run() error; change this so that the
restore result is ignored or handled non-fatally (e.g., assign to let _ =
ratatui::try_restore(); or match and log the restore error) and then return the
original result variable so any Err from run() is always surfaced; update the
code around the run/try_restore calls (references: run, ratatui::try_restore,
result, terminal, cli) to ensure try_restore failure does not overwrite result.
| match key.code { | ||
| KeyCode::Char('q') => { | ||
| app.should_quit = true; | ||
| } | ||
| KeyCode::Tab => app.next_tab(), | ||
| KeyCode::BackTab => app.prev_tab(), | ||
| KeyCode::Up => app.scroll_up(), | ||
| KeyCode::Down => app.scroll_down(), | ||
| KeyCode::Char('r') => { | ||
| app.refresh(&rpc).await; | ||
| last_tick = Instant::now(); | ||
| } | ||
| _ => {} | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if app.should_quit { | ||
| break; |
There was a problem hiding this comment.
Esc and j/k shortcuts are documented in AGENTS.md but not handled here.
AGENTS.md (lines 93–95) lists Esc as a quit key and j/k as scroll keys, but neither KeyCode::Esc, KeyCode::Char('j'), nor KeyCode::Char('k') appear in the match arm. Either add the missing handlers or update the documentation.
🛠️ Proposed fix (add missing handlers)
KeyCode::Down => app.scroll_down(),
+KeyCode::Char('j') => app.scroll_down(),
+KeyCode::Char('k') => app.scroll_up(),
KeyCode::Char('r') => {
app.refresh(&rpc).await;
last_tick = Instant::now();
}
+KeyCode::Esc => {
+ app.should_quit = true;
+}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| match key.code { | |
| KeyCode::Char('q') => { | |
| app.should_quit = true; | |
| } | |
| KeyCode::Tab => app.next_tab(), | |
| KeyCode::BackTab => app.prev_tab(), | |
| KeyCode::Up => app.scroll_up(), | |
| KeyCode::Down => app.scroll_down(), | |
| KeyCode::Char('r') => { | |
| app.refresh(&rpc).await; | |
| last_tick = Instant::now(); | |
| } | |
| _ => {} | |
| } | |
| } | |
| } | |
| } | |
| if app.should_quit { | |
| break; | |
| match key.code { | |
| KeyCode::Char('q') => { | |
| app.should_quit = true; | |
| } | |
| KeyCode::Tab => app.next_tab(), | |
| KeyCode::BackTab => app.prev_tab(), | |
| KeyCode::Up => app.scroll_up(), | |
| KeyCode::Down => app.scroll_down(), | |
| KeyCode::Char('j') => app.scroll_down(), | |
| KeyCode::Char('k') => app.scroll_up(), | |
| KeyCode::Char('r') => { | |
| app.refresh(&rpc).await; | |
| last_tick = Instant::now(); | |
| } | |
| KeyCode::Esc => { | |
| app.should_quit = true; | |
| } | |
| _ => {} | |
| } | |
| } | |
| } | |
| } | |
| if app.should_quit { | |
| break; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@cli/src/main.rs` around lines 76 - 95, The match on key.code is missing
handlers for KeyCode::Esc and the vim-style keys KeyCode::Char('j') and
KeyCode::Char('k') referenced in AGENTS.md; update the match in the block
handling input (the match key.code) to add KeyCode::Esc => { app.should_quit =
true; }, KeyCode::Char('j') => app.scroll_down(), and KeyCode::Char('k') =>
app.scroll_up() so they map to the existing app.should_quit, app.scroll_down,
and app.scroll_up behaviors.
| @@ -20,6 +108,11 @@ Term Challenge is a WASM evaluation module for AI agents on the Bittensor networ | |||
| - **Top Agent Decay**: 72h grace period, 50% daily decay to 0 weight | |||
| - **P2P Dataset Consensus**: Validators collectively select 50 evaluation tasks | |||
There was a problem hiding this comment.
Stale feature claim: "P2P Dataset Consensus" is listed but the functionality was removed.
wasm/src/dataset.rs now only contains placeholder comments — the consensus functions (select_random_task_indices, store_my_selection, build_consensus_dataset, create_dataset_selection) were all removed. This line should either be removed or reworded (e.g., "P2P Dataset Consensus (planned)") to avoid misleading readers.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@README.md` at line 109, The README still advertises "P2P Dataset Consensus"
even though the related functions were removed from wasm/src/dataset.rs
(select_random_task_indices, store_my_selection, build_consensus_dataset,
create_dataset_selection); update the README to either remove this bullet or
mark it as planned/placeholder (e.g., "P2P Dataset Consensus (planned)"), and
ensure the copy matches current code state so readers aren't misled about
existing functionality.
| │ ├── tasks.rs # Active dataset management | ||
| │ ├── dataset.rs # Dataset selection consensus |
There was a problem hiding this comment.
dataset.rs description is misleading.
Line 144 describes dataset.rs as "Dataset selection consensus," but the file now only contains placeholder comments and no functional code. Consider updating the description to reflect its current state (e.g., "Dataset selection consensus (placeholder)") or removing it from the tree.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@README.md` around lines 143 - 144, The README entry for dataset.rs is
inaccurate because dataset.rs currently only contains placeholder comments;
update the README.md description for "dataset.rs" to indicate its placeholder
status (e.g., change "Dataset selection consensus" to "Dataset selection
consensus (placeholder)") or remove the dataset.rs line from the tree; locate
the README.md listing that references dataset.rs and edit the descriptive text
accordingly so it matches the current state of the dataset.rs file.
| /// Retrieve stored agent code for a miner/epoch. | ||
| /// Called by platform-v2's challenge route handler for `/agent/:hotkey/code` requests. | ||
| #[allow(dead_code)] | ||
| pub fn get_agent_code(miner_hotkey: &str, epoch: u64) -> Option<Vec<u8>> { | ||
| let key = make_key(b"agent_code:", miner_hotkey, epoch); | ||
| let data = host_storage_get(&key).ok()?; | ||
| if data.is_empty() { | ||
| return None; | ||
| } | ||
| Some(data) | ||
| } | ||
|
|
||
| /// Retrieve stored agent logs for a miner/epoch. | ||
| /// Called by platform-v2's challenge route handler for `/agent/:hotkey/logs` requests. | ||
| #[allow(dead_code)] | ||
| pub fn get_agent_logs(miner_hotkey: &str, epoch: u64) -> Option<AgentLogs> { | ||
| let key = make_key(b"agent_logs:", miner_hotkey, epoch); | ||
| let data = host_storage_get(&key).ok()?; | ||
| if data.is_empty() { | ||
| return None; | ||
| } | ||
| bincode::deserialize(&data).ok() | ||
| } |
There was a problem hiding this comment.
#[allow(dead_code)] on retrieval functions violates the coding guidelines.
Both get_agent_code and get_agent_logs are annotated with #[allow(dead_code)] pending route-handler integration. The project rule is explicit: "Do NOT add #[allow(dead_code)]. Fix unused code or remove it." Since these functions are pub, the Rust dead-code lint won't fire for them in a library crate anyway, making the attribute doubly unnecessary.
Remove the attributes; the doc comments already document the intended future use.
🛠️ Proposed fix
/// Retrieve stored agent code for a miner/epoch.
/// Called by platform-v2's challenge route handler for `/agent/:hotkey/code` requests.
-#[allow(dead_code)]
pub fn get_agent_code(miner_hotkey: &str, epoch: u64) -> Option<Vec<u8>> { /// Retrieve stored agent logs for a miner/epoch.
/// Called by platform-v2's challenge route handler for `/agent/:hotkey/logs` requests.
-#[allow(dead_code)]
pub fn get_agent_logs(miner_hotkey: &str, epoch: u64) -> Option<AgentLogs> {As per coding guidelines: "Do NOT add #[allow(dead_code)] broadly. Fix unused code or remove it."
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /// Retrieve stored agent code for a miner/epoch. | |
| /// Called by platform-v2's challenge route handler for `/agent/:hotkey/code` requests. | |
| #[allow(dead_code)] | |
| pub fn get_agent_code(miner_hotkey: &str, epoch: u64) -> Option<Vec<u8>> { | |
| let key = make_key(b"agent_code:", miner_hotkey, epoch); | |
| let data = host_storage_get(&key).ok()?; | |
| if data.is_empty() { | |
| return None; | |
| } | |
| Some(data) | |
| } | |
| /// Retrieve stored agent logs for a miner/epoch. | |
| /// Called by platform-v2's challenge route handler for `/agent/:hotkey/logs` requests. | |
| #[allow(dead_code)] | |
| pub fn get_agent_logs(miner_hotkey: &str, epoch: u64) -> Option<AgentLogs> { | |
| let key = make_key(b"agent_logs:", miner_hotkey, epoch); | |
| let data = host_storage_get(&key).ok()?; | |
| if data.is_empty() { | |
| return None; | |
| } | |
| bincode::deserialize(&data).ok() | |
| } | |
| /// Retrieve stored agent code for a miner/epoch. | |
| /// Called by platform-v2's challenge route handler for `/agent/:hotkey/code` requests. | |
| pub fn get_agent_code(miner_hotkey: &str, epoch: u64) -> Option<Vec<u8>> { | |
| let key = make_key(b"agent_code:", miner_hotkey, epoch); | |
| let data = host_storage_get(&key).ok()?; | |
| if data.is_empty() { | |
| return None; | |
| } | |
| Some(data) | |
| } | |
| /// Retrieve stored agent logs for a miner/epoch. | |
| /// Called by platform-v2's challenge route handler for `/agent/:hotkey/logs` requests. | |
| pub fn get_agent_logs(miner_hotkey: &str, epoch: u64) -> Option<AgentLogs> { | |
| let key = make_key(b"agent_logs:", miner_hotkey, epoch); | |
| let data = host_storage_get(&key).ok()?; | |
| if data.is_empty() { | |
| return None; | |
| } | |
| bincode::deserialize(&data).ok() | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@wasm/src/agent_storage.rs` around lines 44 - 66, Remove the unnecessary
#[allow(dead_code)] attributes from the retrieval functions get_agent_code and
get_agent_logs; these functions are public and documented so the dead-code lint
is not applicable, so simply delete the #[allow(dead_code)] lines above each
function (keeping the doc comments and function bodies unchanged).
| pub fn truncate_output(output: &str, max_len: usize) -> String { | ||
| if output.len() <= max_len { | ||
| return String::from(output); | ||
| } | ||
| let truncated = &output[..max_len]; | ||
| String::from(truncated) |
There was a problem hiding this comment.
Critical: output[..max_len] will panic if max_len falls mid-UTF-8 character.
Direct byte-indexing into a &str panics when the index is not on a character boundary. Agent outputs may contain non-ASCII text (error messages, stack traces, unicode identifiers), and with MAX_TASK_OUTPUT_PREVIEW = 4_096 bytes any multi-byte character straddling byte 4 096 will cause a WASM trap during evaluate(), losing the miner's score entirely.
str::is_char_boundary is available in no_std (it is a core function) and fixes this cleanly:
🐛 Proposed fix
pub fn truncate_output(output: &str, max_len: usize) -> String {
if output.len() <= max_len {
return String::from(output);
}
- let truncated = &output[..max_len];
- String::from(truncated)
+ // Walk back to the nearest valid UTF-8 char boundary
+ let mut end = max_len;
+ while end > 0 && !output.is_char_boundary(end) {
+ end -= 1;
+ }
+ String::from(&output[..end])
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| pub fn truncate_output(output: &str, max_len: usize) -> String { | |
| if output.len() <= max_len { | |
| return String::from(output); | |
| } | |
| let truncated = &output[..max_len]; | |
| String::from(truncated) | |
| pub fn truncate_output(output: &str, max_len: usize) -> String { | |
| if output.len() <= max_len { | |
| return String::from(output); | |
| } | |
| // Walk back to the nearest valid UTF-8 char boundary | |
| let mut end = max_len; | |
| while end > 0 && !output.is_char_boundary(end) { | |
| end -= 1; | |
| } | |
| String::from(&output[..end]) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@wasm/src/agent_storage.rs` around lines 68 - 73, The truncate_output function
currently slices the &str with output[..max_len] which can panic if max_len
falls mid-UTF-8 character; update truncate_output to compute a safe byte index
<= max_len that is on a character boundary (use str::is_char_boundary or iterate
char_indices to find the last valid boundary <= max_len) and then slice using
that safe index (e.g., &output[..safe_len]) before converting to String; keep
the original fast-path when output.len() <= max_len and ensure the function
returns a valid UTF-8 String without panicking.
| /// Returns route definitions for the term-challenge module. | ||
| /// | ||
| /// Note: This function is currently unused pending integration with platform-v2's | ||
| /// challenge route registration system. The routes are defined here for documentation | ||
| /// and future automatic registration. | ||
| #[allow(dead_code)] |
There was a problem hiding this comment.
#[allow(dead_code)] violates the coding guidelines — remove it.
The doc comment already explains why the function is currently unused. Since get_route_definitions is already pub, the dead_code lint won't fire for it in a normal library crate. Adding #[allow(dead_code)] is unnecessary here and contradicts the project rule "Do NOT add #[allow(dead_code)]. Fix unused code or remove it."
🛠️ Proposed fix
-/// Note: This function is currently unused pending integration with platform-v2's
-/// challenge route registration system. The routes are defined here for documentation
-/// and future automatic registration.
-#[allow(dead_code)]
pub fn get_route_definitions() -> Vec<RouteDefinition> {As per coding guidelines: "Do NOT add #[allow(dead_code)] broadly. Fix unused code or remove it."
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /// Returns route definitions for the term-challenge module. | |
| /// | |
| /// Note: This function is currently unused pending integration with platform-v2's | |
| /// challenge route registration system. The routes are defined here for documentation | |
| /// and future automatic registration. | |
| #[allow(dead_code)] | |
| /// Returns route definitions for the term-challenge module. | |
| /// | |
| /// Note: This function is currently unused pending integration with platform-v2's | |
| /// challenge route registration system. The routes are defined here for documentation | |
| /// and future automatic registration. | |
| pub fn get_route_definitions() -> Vec<RouteDefinition> { |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@wasm/src/routes.rs` around lines 13 - 18, Remove the unnecessary
#[allow(dead_code)] attribute on the get_route_definitions function: delete that
attribute from the function definition (leave the pub fn
get_route_definitions(...) and its doc comment intact) so the crate follows the
rule against broad #[allow(dead_code)] usage; if the function really is unused
and you intend to keep it, consider marking it pub(crate) or wiring up its
registration, but do not add #[allow(dead_code)].
Summary
Adds a native ratatui-based TUI binary (
term-cli) for real-time chain monitoring, introduces on-chain agent code/log storage in the WASM module with size-enforced limits, and updates project documentation with Mermaid architecture diagrams.Changes
CLI (
cli/)term-clibinary crate with ratatui TUI featuring four tabs: Leaderboard, Live Evaluation, Agent Submission, and Network Statusrpc.rs) for connecting to platform-v2 RPC endpoints with support forchallenge_call,epoch_current,system_health,validator_list, andchallenge_listmethodsapp.rs) with async data fetching and tab-based view rendering (ui.rs)WASM Agent Storage (
wasm/src/agent_storage.rs)store_agent_code()— stores compressed agent zip packages (≤1MB enforced) keyed byagent_code:<hotkey>:<epoch>store_agent_logs()— stores evaluation logs (≤256KB enforced) keyed byagent_logs:<hotkey>:<epoch>, with per-task output truncated to 4KBget_agent_code()/get_agent_logs()— retrieval functions usinghost_storage_getno_stdcompatible usingalloctypesWASM Module Changes (
wasm/src/)AgentLogEntryandAgentLogstypes totypes.rsfor structured log storage/agent/:hotkey/codeand/agent/:hotkey/logsroute definitions inroutes.rsevaluate()inlib.rsto store agent code and truncated logs after evaluationvalidate()to reject submissions withpackage_zipexceeding 1MBdataset.rsandtasks.rs, added doc comments throughoutcalculate_aggregateandto_weightdoc comments inscoring.rsDocumentation
README.md: Added system architecture, evaluation flow, CLI data flow, log consensus, and agent storage Mermaid diagrams; expanded project overview and workspace structure docsAGENTS.md: Added CLI development guidelines, agent storage conventions, and updated project rules for the new cratesCargo.tomlto includecliin workspace members withdefault-membersset towasmonly (prevents CLI from being built under wasm32 target)Summary by CodeRabbit
Release Notes
New Features
Documentation