nvidia-nemo/labs-oo-agents · Archived

refine-agent-prompt

>- Render and refine the system prompt of a nooa agent. Use when the user wants to inspect, debug, or improve an agent's prompt, context blocks, or tool documentation.

First seen Aug 13, 2026

Installation

$ npx skills add nvidia-nemo/labs-oo-agents --skill refine-agent-prompt

Stronger alternatives

This repository is archived — consider an actively maintained alternative.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from nvidia-nemo/labs-oo-agents · top by installs.

npx skills add nvidia-nemo/labs-oo-agents

Browse all from nvidia-nemo/labs-oo-agents

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 1.5K
License LICENSE
Default branch main
Open issues 44
Status Archived

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 10,662 B
  • docs SUMMARY.md 191 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 1 installs

SKILL.md

Refine Agent Prompt

Render the exact prompt that would be sent to the LLM — with real agent state, real context blocks, and real arguments — then work through diagnostic questions to find waste.

Step 1: Render the prompt

Use nooa.print_prompt with a FakeLLMClient to render without making a real LLM call:

import asyncio, nooa
from nooa import Agent
from nooa.unifiedllm import FakeLLMClient

class DemoAgent(Agent, llm=FakeLLMClient()):
    async def respond(self, message: str) -> str:
        """Answer the user's message."""
        ...

async def main():
    agent = DemoAgent()
    await nooa.print_prompt(agent.respond, "example user message here")

asyncio.run(main())

For a custom agent loaded from a file:

import asyncio, importlib.util, sys, nooa
from nooa.unifiedllm import FakeLLMClient

spec = importlib.util.spec_from_file_location("agent_module", "path/to/my_agent.py")
mod = importlib.util.module_from_spec(spec)
sys.modules["agent_module"] = mod
spec.loader.exec_module(mod)

async def main():
    agent = mod.MyAgent(llm=FakeLLMClient())
    await nooa.print_prompt(agent.respond, "example user message here")

asyncio.run(main())

Run with: uv run python3 << 'EOF' ... EOF

To render a specific sub-method (e.g. answerquestion, brainstorm, implementstep):

await nooa.print_prompt(agent.answer_question, "what is the capital of France?")
await nooa.print_prompt(agent.implement_step, '{"description": "add logging"}')

To capture the data programmatically instead of printing:

data = await nooa.build_prompt_data(agent.respond, "hello")
print(data.system_prompt)   # the full system message
print(data.task_prompt)     # the task user-message
print(data.inspect_prefill) # the inspect/prefill code block

Step 2: Diagnostic questions

Work through these one by one. Each has tradeoffs — discuss with the user before changing.


Is the execution context clean?

The <execution_context> block lists every symbol available in the REPL. Scan Imported items and ask: does the LLM have any reason to call this directly?

Should usually be hidden: framework internals (SkillManager, TokenBudgetSummarizer), base classes (BaseTUIAgent, Agent), LLM client classes (FakeLLMClient, getllmclient), internal config (CodeActConfig, SummarizationConfig, AgentConfig), pydantic internals (BaseModel, Field), private helpers (anything starting with _).

Fine to expose: tool/skill classes the LLM uses (ShellTools, WebPublisher), pd, np, px, go, Path, json, math, re.

Fix: wrap the import in with hidden: at module level:

from nooa import hidden   # must be outside

with hidden:
    from nooa.agents import TokenBudgetSummarizer
    from nooa.config import CodeActConfig

Tradeoff: hiding something means the LLM can't use it by name in the REPL. Only hide things the agent has no reason to call directly.


Is there duplicate content?

Look for the same information in more than one place.

  • Same class in <self> (Referenced Types) AND as a context block or skill doc:

usually OK if <self> shows a concise one-liner. If both are full docs, that's waste.

  • Task prompt repeating a workflow already in a context block:

usually removable — the context block is dynamic, the docstring is stale.

  • Skill docstring examples already shown in the task prompt: consider whether the

repetition is reinforcing a critical pattern or just inflating tokens.


Are internal methods visible?

Scroll <self> for methods the LLM should never call directly: getsummarizationstatus(), respondcodeact(), phase, workflowstate, config, install_summarizer(), etc.

Fix: @hidden from nooa:

from nooa import hidden
from nooa import Agent

class MyAgent(Agent, llm=...):

    @hidden
    def get_summarization_status(self) -> dict: ...

Fields can be hidden with the hidden marker from nooa.storage.markers:

from typing import Annotated
from nooa import hidden
from nooa.storage.markers import nosnapshot

class MyAgent(Agent, llm=...):
    _phase: Annotated[str, hidden]
    _workflow_state: Annotated[dict, hidden]
    bash: Annotated[ShellTools, nosnapshot]       # visible but not snapshotted

Tradeoff: none for internal lifecycle state. If the LLM is calling _phase directly, that's a prompt problem to fix, not a reason to keep it visible.


Is the main entry point hidden?

If the agent has a primary method the LLM is called as rather than calling, it will appear in <self> — but the LLM never needs to call itself recursively.

Fix: @hidden the entry-point so <self> shows only attributes the LLM uses:

@hidden
async def respond(self, user_message: str) -> None:
    """..."""
    ...

Tradeoff: none for single-method agents. For orchestrators with multiple callable sub-methods, only hide the outermost entry point.


Is the task prompt (docstring) clear and non-redundant?

Read the method docstring as if you're the LLM seeing it fresh:

  • Does it repeat tool usage already shown in Skills or context blocks?

Replace with a pointer: "See doc(self.bash) for shell commands."

  • Are the output rules (format, structure) specific and unambiguous?
  • **Does a raw method argument such as {user_message} appear as a template

substitution?** That is usually a bug. Check === PREFILL ===: CodeAct already shows arguments under truncation limits, and Predict serializes them with size caps. Interpolation duplicates the raw value without those protections and moves untrusted data into the instruction channel.

  • Use docstring interpolation for trusted instance configuration such as

{self._instructions} and bounded computed metadata the signature cannot show—not to repeat ordinary parameters.

Tradeoff: shorter prompts are cheaper, but the LLM may miss instructions that only appear in context blocks it skims. Keep critical constraints in the task prompt even if they're also elsewhere.


Are Skills surfaced well?

Agents with Skill subclass attributes expose them via doc(self.<skill>). The <execution_context> skills table instructs the LLM to call doc() before using each skill.

Check:

  • Is the skill's 1-liner description clear enough for the LLM to know when to call doc()?
  • Does the skill's full docstring (revealed by doc()) have enough examples for the LLM

to use it correctly in one shot, without needing follow-up calls?

  • Is any skill API already fully replicated in the task prompt? If so, that's waste —

the skill docstring is the canonical source.

For WebPublisher / self.web: the agent should be able to produce a working Plotly call on the first try. If it takes multiple LLM iterations, check whether the skill examples cover the user's data shape (long-form vs wide-form DataFrames, the right px.* function, axis mapping). Add more targeted examples to the skill docstring.


Are return types and data structures strongly typed?

Look for string-in / string-out patterns where typed Pydantic models would be clearer:

# Instead of:
async def classify_intent(self, msg: str) -> str:
    """Returns 'question', 'feature', 'bugfix', or 'refactor'"""

# Use Pydantic — fields render as a schema in the prompt:
from pydantic import BaseModel
from typing import Literal

class Intent(BaseModel):
    """Classification of the user's request."""
    task_type: Literal["question", "feature", "bugfix", "refactor"]
    summary: str  # One-sentence description of what the user wants.

async def classify_intent(self, msg: str) -> Intent:
    ...

Pydantic models render field types, defaults, and docstrings automatically in <self> and Referenced Types — the LLM gets a clear schema without extra documentation effort.

Use Field constraints and validators for rules that depend only on the returned value. If correctness depends on a file, database row, API response, or saved artifact, enforce that with a deterministic Python gate before the orchestrator accepts the result.


Are context blocks vs inline {doc(...)} used correctly?

The task prompt can embed tool docs inline or store them in a persistent context block:

  • Short-running methods (< 1 min, few turns): inline {doc(self.tool)} is fine.
  • Long-running methods (many turns, large context, at risk of summarization):

prefer self.context["key"] = Context(doc(self.tool), prefix=True) so the docs persist across summarization in the stable prompt prefix.

from nooa import Context

# Inline (good for short tasks):
async def respond(self, user_message: str) -> None:
    """Answer the user using the shell API below.

    {doc(self.bash)}
    """

# Context block (good for long tasks):
def __init__(self, **kwargs):
    super().__init__(**kwargs)
    self.context["bash_docs"] = Context(doc(self.bash), prefix=True)

Tradeoff: context blocks add overhead every turn; inline is paid once at task start.


nooa reference

@hidden — hide a method from <self> and the execution context

from nooa import hidden

@hidden
def get_summarization_status(self): ...

with hidden:
    from nooa.agents import TokenBudgetSummarizer
    from nooa.config import CodeActConfig

Annotated[T, hidden] — hide a field from <self>

from nooa import hidden
from nooa.storage.markers import nosnapshot
from typing import Annotated

class MyAgent(Agent, llm=...):
    _phase: Annotated[str, hidden]
    bash: Annotated[ShellTools, nosnapshot]   # shown but not persisted to snapshots

nosnapshot — exclude a field from session snapshots

from nooa.storage.markers import nosnapshot
from typing import Annotated

bash: Annotated[ShellTools, nosnapshot]   # live tools shouldn't be serialised

nooa.print_prompt — render without an LLM call

await nooa.print_prompt(agent.my_method, arg1, arg2)

nooa.buildpromptdata — capture prompt sections programmatically

data = await nooa.build_prompt_data(agent.my_method, arg1)
data.system_prompt   # full system message
data.task_prompt     # task user-message
data.inspect_prefill # prefill/inspect code
data.strategy_name   # e.g. 'CodeActStrategy'