Skip to main content
Use afk.debugger.Debugger to run agents in a structured debug mode without changing your core runtime architecture.

Quick start

from afk.debugger import Debugger, DebuggerConfig

debugger = Debugger(
    DebuggerConfig(
        verbosity="detailed",
        redact_secrets=True,
        include_content=True,
    )
)

runner = debugger.runner()
You can also enable debug mode directly on the runner:
from afk.core import Runner, RunnerConfig

runner = Runner(config=RunnerConfig(debug=True))

Attach to a run handle

handle = await runner.run_handle(agent, user_message="inspect this run")
await debugger.attach(handle)  # prints formatted events

Redaction behavior

When redact_secrets=True, payload keys containing any of these markers are masked:
  • api_key
  • token
  • secret
  • authorization
  • password

Verbosity modes

  • basic: lightweight metadata and step markers.
  • detailed: includes payload previews and step metadata.
  • trace: highest detail for deep diagnostics.

Background tool scenario (coding + build + docs)

from afk.tools import tool, ToolResult, ToolDeferredHandle

@tool(args_model=BuildArgs, name="build_project")
async def build_project(args: BuildArgs) -> ToolResult[dict[str, str]]:
    future = asyncio.create_task(run_long_build(args.path))
    return ToolResult(
        success=True,
        deferred=ToolDeferredHandle(
            ticket_id="build-123",
            tool_name="build_project",
            status="running",
            summary="Build started",
            resume_hint="Continue documentation while build runs",
        ),
        metadata={"background_task": future},
    )
Flow:
  1. Agent writes code.
  2. Agent calls build_project and receives tool_deferred.
  3. Agent continues with write_docs or other tasks.
  4. Runner emits tool_background_resolved when build completes.
  5. Agent uses resolved build output in a later step to finalize/fix.

External worker completion

For out-of-process execution, an external worker can complete a ticket by writing background state into memory:
await memory.put_state(
    thread_id,
    f"bgtool:{run_id}:{ticket_id}:state",
    {
        "run_id": run_id,
        "thread_id": thread_id,
        "ticket_id": ticket_id,
        "tool_name": "build_project",
        "status": "completed",  # or "failed"
        "output": {"status": "ok", "artifact": "dist/app"},
        "error": None,
    },
)
The runner poller detects this update, emits tool_background_resolved or tool_background_failed, and injects a synthetic tool message for the next step.