Skip to content

nous status 'last tool' field permanently empty (extracts wrong attribute) #195

Description

@sriumcp

Problem

nous status --line and nous status --watch promise a "last tool" indicator so the operator can see what the agent just ran (e.g., last=Bash, last=Edit). In production it's always empty.

Repro

Run any SDK-backed campaign and check status mid-run:

nous status <work_dir> --line
# Output: "<run> · DESIGN · iter 0 · 0 done · 0 principles"  ← no `last=` field

Look at the streaming log directly — there are plenty of tool calls happening:

tail -2 <work_dir>/runs/iter-1/inputs/executor_log.jsonl
# {"type": "AssistantMessage", "ts": ..., "content": "[ToolUseBlock(id='...', name='Bash', input={...})]"}

So the data exists; the status reader just isn't extracting it.

Root cause

orchestrator/sdk_dispatch.py::_tee_event (the function that records each SDK message to executor_log.jsonl) reads:

for field_name in ("tool_name", "tool_use_id", "content"):
    val = getattr(message, field_name, None)
    if val is not None and not callable(val):
        ...
        record[field_name] = val

But on an AssistantMessage, tool_name is not a top-level attribute. It lives on ToolUseBlock instances inside message.content (a list of content blocks: TextBlock, ThinkingBlock, ToolUseBlock). So getattr(message, "tool_name", None) always returns None, and the JSONL record never contains a tool_name field.

orchestrator/status.py::format_one_liner then looks for tool = snap.last_event.get("tool_name") or snap.last_event.get("tool"), finds nothing, and renders the line without a last= token.

Fix

In _tee_event, walk the content blocks and extract tool names:

content = getattr(message, "content", None)
if isinstance(content, list):
    tool_names = []
    for block in content:
        name = getattr(block, "name", None) or getattr(block, "tool_name", None)
        if name:
            tool_names.append(name)
    if tool_names:
        # Last tool wins (most recent) — or join all
        record["tool_name"] = tool_names[-1]

Files to touch

  • orchestrator/sdk_dispatch.py::_tee_event — extract tool_name from ToolUseBlock entries.
  • tests/test_sdk_dispatch.py — assert that after a dispatch with a Bash tool call, the JSONL row has tool_name == "Bash".
  • tests/test_status.py — drive a fixture log with a Bash event, assert format_one_liner includes last=Bash.

Discovered in

paper-burst friction-test, 2026-05-26.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions