Augments LabsAugments ADK

Verbose Output

Colourful, event-driven tracing of agent runs. Inspired by CrewAI's console output, but with two backends (stateless line + stateful panel), developer-chosen mode resolution, and ADK-first-class event vocabulary (HITL, budgets, cache, context, turn boundaries, streaming markers, typed retries).

Augments = "work of art". Verbose output is where that promise lives on the terminal.

Quick start

import asyncio
import logging
 
from augments.adk import Agent, Runner, RunConfig, VerboseConfig
 
logger = logging.getLogger(__name__)
 
agent = Agent(
    name="Assistant",
    llm="gpt-4o-mini",
    system_prompt="Answer concisely.",
)
 
async def main() -> None:
    result = await Runner.arun(
        agent,
        "What is the capital of France?",
        run_config=RunConfig(verbose=VerboseConfig()),
    )
    logger.info("Final: %s", result.final_output)
 
asyncio.run(main())

Default VerboseConfig() resolves to mode="auto": Rich panels on an interactive TTY, plain lines in CI or when stdout is piped / redirected to a file. No code changes required across environments.

Backends

BackendWhen it runsWhat it looks likeStability
lineAlways safe; picked in non-TTY, CI, NO_COLOR, TERM=dumb, or when Rich isn't installedOne coloured line per event on stderrByte-for-byte stable โ€” the original Augments verbose output
panelInteractive TTY + Rich installed + not CIBordered Rich panels per logical block (๐Ÿ“‹ Task, ๐Ÿค– Agent, ๐Ÿ”ง Tool, โœ… Final Answer) with event-kind border colours and a live-updating streaming panelCrewAI-faithful โ€” mirrors ConsoleFormatter verbatim

Select explicitly with VerboseConfig(mode=...):

VerboseConfig(mode="auto")     # default, environment-aware
VerboseConfig(mode="line")     # force line renderer
VerboseConfig(mode="panel")    # force Rich panels (requires Rich)
VerboseConfig(mode="off")      # emit nothing

Mode resolution ladder

resolve_mode(config) walks this ladder top-to-bottom and returns the first match:

  1. config.enabled is False โ†’ off
  2. mode == "off" โ†’ off
  3. mode == "line" โ†’ line
  4. mode == "panel" + Rich missing โ†’ line (with DEBUG log)
  5. mode == "panel" โ†’ panel
  6. auto mode only โ€” NO_COLOR env โ†’ line
  7. auto mode only โ€” FORCE_COLOR env โ†’ panel if Rich else line
  8. auto mode only โ€” output is not a TTY โ†’ line
  9. auto mode only โ€” CI / TERM=dumb โ†’ line
  10. auto mode only โ€” Rich not installed โ†’ line
  11. auto mode default โ†’ panel

Every downgrade logs at DEBUG (configs/logging/default_logger.yaml routes this to the .log file), so operators can verify the resolved mode without adding instrumentation.

Scenario matrix

Scenariomode="auto" picksWhy
Dev laptop, interactive terminalpanelTTY + Rich + not CI
python script.py > run.loglinestdout redirected, no TTY
python script.py | catlinepipe, no TTY
GitHub Actions / GitLab CIlineCI=1 env
NO_COLOR=1 python script.pylineNO_COLOR standard
TERM=dumb python script.pylinedumb terminal
Docker container, Rich installed, interactivepanelTTY + Rich
Docker container, Rich missinglineRich not importable

See examples/verbose/ci_safe.py for a script that prints the resolved mode + its inputs at startup.

Interaction with the classic logger

VerboseConfig writes to a TextIO stream (default sys.stderr) โ€” it does not go through Python's logging module. The two channels are fully independent by design.

The default logger configuration (configs/logging/default_logger.yaml) attaches no console handler: logger.* records go only to the rotating .log file. The terminal therefore belongs entirely to the verbose event stream โ€” there is no collision to manage in a standard setup.

These are two independent channels:

ChannelSourceDestinationConfig
Verbose outputVerboseRenderer.render_line() / PanelRenderer.close_block()stderr (or explicit TextIO)VerboseConfig
Structured logslogger.info/debug/...Rotating .log file (file-only by default)configs/logging/*.yaml

The programmatic fallback โ€” used when the YAML file is absent or PyYAML is not installed โ€” installs a logging.NullHandler, following standard library practice. No log records reach the terminal in that path either.

Re-enabling console log output. If you want logger.* records on the terminal as well, uncomment the console handler definition in configs/logging/default_logger.yaml and add console to the handlers: list for each logger you want it on. Accept that log lines and verbose event lines will interleave on the same terminal; this is a deliberate operator choice, not the default.

If you want verbose output in the same file as your structured logs, open the file yourself and pass it via VerboseConfig(output=...).

Per-event control

Every event carries an EventStyle โ€” customise colour, icon, prefix, and whether the payload (tool args, LLM message bodies, guardrail results) is shown.

from augments.adk.verbose import VerboseConfig, EventStyle
from augments.adk.verbose.config import EVENT_TOOL_START, EVENT_LLM_START
 
cfg = VerboseConfig()
cfg.styles[EVENT_TOOL_START] = EventStyle(
    color="bright_magenta", icon="โ–ถ", prefix="tool",
)
cfg.styles[EVENT_LLM_START] = EventStyle()  # empty style = mute

Per-agent override

Set Agent.verbose to override the run-level config for one agent. Useful in multi-agent swarms to make one agent loud and another silent.

from augments.adk import Agent
from augments.adk.verbose import VerboseConfig
 
coordinator = Agent(name="Coordinator", llm="gpt-4o", verbose=VerboseConfig())
summariser = Agent(
    name="Summariser",
    llm="gpt-4o-mini",
    verbose=VerboseConfig(enabled=False),
)

The renderer resolves at emit time: Agent.verbose wins over RunConfig.verbose. A per-agent config with enabled=False silences that agent while the rest of the run keeps its styling.

Event-kind border colour (panel mode)

Border colours are fixed per event kind, not derived from a verdict string. This matches CrewAI's ConsoleFormatter: each panel type has a recognisable colour signature so the user can scan output without reading titles:

EventPanel titleBorder
task.start๐Ÿ“‹ Task Startedyellow
task.end๐Ÿ“‹ Task Completedgreen
task.failedโŒ Task Failedred
agent.start๐Ÿค– Agent Startedmagenta
agent.finishโœ… Agent Final Answergreen
tool.start๐Ÿ”ง Tool Execution Started (#N)yellow
tool.error(inherits EventStyle.color)red
run.start๐Ÿš€ Crew Execution Startedcyan
run.endCrew Completiongreen

ADK-only events (HITL, budget, cache, context, MCP, guardrails) without an entry in _EVENT_BORDER fall through to their configured EventStyle.color โ€” they stay extensible and the rest of the palette is locked.

Live streaming

When an LLM call streams (e.g. Runner.arun(..., stream=True)), the panel backend opens a rich.live.Live widget that updates in place as tokens arrive:

  • Final-answer text streams render in the green โœ… Agent Final Answer panel.
  • Tool-call argument deltas (function-call JSON) stream in the yellow ๐Ÿ”ง Tool Arguments panel.

The Live widget refreshes at 10 Hz internally โ€” chunk emission is cheap. Per-chunk events go straight to VerboseHooks (not through CompositeRunHooks fan-out) so user-installed hooks are not woken on every token.

After a text stream finishes, the renderer suppresses the duplicate agent.finish block panel (the Live widget already painted the answer) via a _just_streamed_final_answer flag.

pause_live_for_hitl / resume_live_for_hitl stop and restart the widget around HITL approval prompts so the stdin read does not race the refresh loop.

Task boundary

Every outer Runner.arun() / Runner.arun_swarm() call emits a ๐Ÿ“‹ Task panel pair:

  1. ๐Ÿ“‹ Task Started โ€” yellow border, contains the user prompt (truncated to 80 chars) and an 8-char task ID.
  2. ๐Ÿ“‹ Task Completed (green) or โŒ Task Failed (red, with error string) โ€” fires once the run returns or raises.

The line backend emits a task started: โ€ฆ / task completed: โ€ฆ line for CI log compatibility.

Event reference

Emitted events

ConstantEvent nameFires at
EVENT_AGENT_STARTagent.startEach agent turn begins
EVENT_AGENT_ENDagent.endEach agent turn ends
EVENT_LLM_STARTllm.startBefore every LLM call
EVENT_LLM_ENDllm.endAfter every LLM call
EVENT_TOOL_STARTtool.startBefore every tool invocation
EVENT_TOOL_ENDtool.endAfter every tool invocation
EVENT_TOOL_ERRORtool.errorTool raised; panel closes red
EVENT_HANDOFFhandoffOn each agent-to-agent handoff
EVENT_GUARDRAIL_INPUT_START/ENDguardrail.input.*Agent-level input guardrail
EVENT_GUARDRAIL_OUTPUT_START/ENDguardrail.output.*Agent-level output guardrail
EVENT_SKILL_ACTIVATEDskill.activatedWhen a skill activates
EVENT_SESSION_LOAD/SAVEsession.*Session history load/save
EVENT_TURN_START/ENDturn.*Agent-loop turn boundaries
EVENT_USAGE_RECORDEDusage.recordedCumulative tokens after each LLM call
EVENT_CACHE_HIT/MISScache.*Prompt/tool cache
EVENT_RETRYretryTool ToolRetry caught
EVENT_CONTEXT_COMPACTEDcontext.compactedContext manager summarized history
EVENT_STREAM_START/ENDstream.*Streaming window
EVENT_HITL_APPROVAL_REQUESTEDhitl.approval.requestedTool deferred for approval
EVENT_HITL_APPROVAL_GRANTEDhitl.approval.grantedRunState.approve() resume
EVENT_HITL_APPROVAL_REJECTEDhitl.approval.rejectedRunState.reject() resume
EVENT_BUDGET_EXCEEDEDbudget.exceededUsageLimitExceeded raised

Tool-level guardrails

Four additional RunHooks methods that fire around each tool's input/output guardrail chain:

  • on_tool_input_guardrail_start(ctx, tool, guardrail)
  • on_tool_input_guardrail_end(ctx, tool, guardrail, result)
  • on_tool_output_guardrail_start(ctx, tool, guardrail)
  • on_tool_output_guardrail_end(ctx, tool, guardrail, result)

VerboseHooks overrides each to emit a scoped guardrail panel keyed by (tool_name, guardrail_name, kind), so agent-level and tool-level guardrails render as distinct panels without colliding. Verdicts are derived from the ToolGuardrailFunctionOutput.behavior["type"]:

behavior["type"]VerdictBorder
allowpassgreen
reject_contenttripred
raise_exceptiontripred

Task Boundary Events

Task boundary panels add these events around each top-level run:

ConstantEvent nameFires at
EVENT_TASK_STARTtask.startRunner.arun() / arun_swarm() entry
EVENT_TASK_ENDtask.endRunner.arun() clean exit
EVENT_TASK_FAILEDtask.failedRunner.arun() exception path

Per-chunk LLM streaming drives the Live widget via the emit_stream_chunk free function plus a ContextVar bridge in augments.adk.verbose.run_bridge.

Reserved Style Entries

Events listed in VerboseConfig.styles but without a call site in the runner. These correspond to feature-specific event surfaces such as Flow API, Memory read/write, Knowledge retrieval, MCP lifecycle, and reasoning tiers.

EVENT_RUN_START, EVENT_RUN_END, EVENT_REASONING_START, EVENT_REASONING_END, EVENT_PLAN_REFINED, EVENT_REPLAN, EVENT_GOAL_ACHIEVED, EVENT_MEMORY_READ, EVENT_MEMORY_WRITE, EVENT_MEMORY_ERROR, EVENT_KNOWLEDGE_QUERY, EVENT_KNOWLEDGE_RESULT, EVENT_MCP_CONNECT, EVENT_MCP_CONNECTED, EVENT_MCP_ERROR, EVENT_FLOW_START, EVENT_FLOW_END, EVENT_FLOW_PAUSED, EVENT_BUDGET_WARNING, EVENT_CONTEXT_EDITED, EVENT_STATE_SAVE, EVENT_WARNING.

Extending to new Agent attributes

The event registry is a plain dict[str, EventStyle]. New attributes that want visibility (hypothetical memory_access, plan_revised, etc.) can register their own events at runtime without changing the renderer:

cfg = VerboseConfig()
cfg.register_event(
    "memory.read",
    EventStyle(color="blue", icon="โ‡ฒ", prefix="memory"),
)

Any emit path that calls renderer.render_line("memory.read", headline, payload) will pick up the style. Unknown events render as plain text without colour โ€” forward compatibility is automatic.

NO_COLOR support

Honours the NO_COLOR standard (https://no-color.org/). Any non-empty value disables colour universally, independent of VerboseConfig.use_color.

NO_COLOR=1 python my_script.py

Examples

See examples/verbose/:

FileDemonstrates
basic.pyDefault config, single agent
basic_panel.pymode="panel" with Rich panels
ci_safe.pymode="auto" reporting the resolved mode + inputs
custom_styles.pyRecolouring and muting events
per_agent.pyPer-agent overrides in a multi-agent flow
hitl.pyHITL approval gate visualization
nested_hitl.pyApprovals bubbling through as_tool()
multi_level_guardrails.pyTool-level + agent-level guardrail panels
streaming.pystream.start / stream.end markers