استخدمه عندما يكون هدفك واضحًا في توسيع قدرات الوكيل وتستطيع تحديد البيانات والأفعال التي يحتاجها فقط.
Python Debugpy
Python Debugpy: مهارة ينشرها Hermes Agent. الرخصة MIT. الإصدار الموثّق 1.0.0. مثبّتة افتراضيًا مع Hermes، فلا تحتاج خطوة تثبيت. تدعم: linux، macos.
ماذا يضيف إلى Hermes؟
Python Debugpy: مهارة ينشرها Hermes Agent. الرخصة MIT. الإصدار الموثّق 1.0.0. مثبّتة افتراضيًا مع Hermes، فلا تحتاج خطوة تثبيت. تدعم: linux، macos.
Python Debugpy هي مهارة مرتبطة بمجال توسيع قدرات الوكيل. يضيف قدرة أو سير عمل إلى Hermes. الوصف الأصلي يحدد التفاصيل، بينما تحدد الصلاحيات ما يستطيع فعله فعليًا.
هذا تفسير مبسّط مبني على وصف الناشر. أبقينا الوصف الإنجليزي بجانبه حتى تستطيع مقارنة المعنى بالمصدر.
لا تضفه لمجرد التجربة إذا كان لديك طريق أبسط داخل Hermes، أو إذا لم تستطع مراجعة المصدر والصلاحيات.
مناسب لمن يريد طريقة عمل قابلة للتكرار داخل Hermes.
ابدأ ببيانات غير حساسة ومهمة صغيرة يمكن التحقق من نتيجتها والتراجع عنها.
Debug Python: pdb REPL + debugpy remote (DAP)
فُهرس هذا الإدخال من Hermes Bundled Skills. الشرح العربي يفسّر النوع والمجال ولا يضيف وظيفة غير مذكورة في المصدر.
المصدر رسمي أو خضع لمراجعة تحريرية، لكن ذلك لا يغني عن مراجعة الصلاحيات والإصدار.
افحص، ثبّت، ثم اختبر.
- 01افتح المصدر
طابق اسم الناشر والرخصة والوصف مع حاجتك، وراجع آخر تحديث فعلي.
- 02راجع الصلاحيات والأسرار
لا تلصق قيمة سر داخل الموقع. استخدم أسماء متغيرات البيئة وامنح أقل نطاق ممكن.
- 03انسخ الإعداد فقط بعد المراجعة
الأزرار أدناه تنسخ نصًا إلى الحافظة ولا تشغّل أمرًا على جهازك.
- 04اختبر بمهمة غير حساسة
تحقق من الأدوات الظاهرة، ثم استبعد أدوات الكتابة أو الحذف التي لا تحتاجها.
مثبّتة مسبقًا مع Hermes.
تأتي هذه المهارة مع Hermes وتُحمَّل عندما يرى الوكيل أنها مناسبة. لا شيء لتثبيته؛ اقرأ التعريف أدناه لتعرف ما ستفعله.
افتح الصفحة الرسمية ↗ما الذي يحمّله Hermes بالضبط عند تشغيل هذه المهارة.
منقول من التوثيق الرسمي. اقرأه قبل تفعيل المهارة، فهذا النص يصبح تعليمات الوكيل نفسه.
Debug Python: pdb REPL + debugpy remote (DAP).
Skill metadata
جدول مرجعي. لا تقرأه كله، ابحث عن السطر الذي يخصّك فقط.
| Source | Bundled (installed by default) |
| Path | skills/software-development/python-debugpy |
| Version | 1.0.0 |
| Author | Hermes Agent |
| License | MIT |
| Platforms | linux, macos |
| Tags | debugging, python, pdb, debugpy, breakpoints, dap, post-mortem |
| Related skills | systematic-debugging, node-inspect-debugger |
Reference: full SKILL.md
شرح للفكرة نفسها. اقرأه ببطء، فبقية الأقسام تبني عليه.
Overview
شرح للفكرة نفسها. اقرأه ببطء، فبقية الأقسام تبني عليه.
Three tools, picked by situation:
| Tool | When |
|---|---|
breakpoint() + pdb | Local, interactive, simplest. Add breakpoint() in the source, run normally, get a REPL at that line. |
python -m pdb | Launch an existing script under pdb with no source edits. Useful for quick poking. |
debugpy | Remote / headless / "attach to already-running process." Talks DAP, scriptable from terminal, works for long-lived processes (gateway, daemon, PTY children). |
Start with breakpoint(). It's the cheapest thing that works.
When to Use
شرح للفكرة نفسها. اقرأه ببطء، فبقية الأقسام تبني عليه.
- A test fails and the traceback doesn't reveal why a value is wrong
- You need to step through a function and watch a collection mutate
- A long-running process (hermes gateway, tui_gateway) misbehaves and you can't restart it
- Post-mortem: an exception fired in prod-ish code and you want to inspect locals at the crash site
- A subprocess / child (Python
_SlashWorker, PTY bridge worker) is the actual bug site
Don't use for: things print() / logging.debug solve in under a minute, or things pytest -vv --tb=long --showlocals already reveals.
pdb Quick Reference
جدول مرجعي. لا تقرأه كله، ابحث عن السطر الذي يخصّك فقط.
Inside any pdb prompt ((Pdb)):
| Command | Action |
|---|---|
h / h cmd | help |
n | next line (step over) |
s | step into |
r | return from current function |
c | continue |
unt N | continue until line N |
j N | jump to line N (same function only) |
l / ll | list source around current line / full function |
w | where (stack trace) |
u / d | move up / down in the stack |
a | print args of the current function |
p expr / pp expr | print / pretty-print expression |
display expr | auto-print expr on every stop |
b file:line | set breakpoint |
b func | break on function entry |
b file:line, cond | conditional breakpoint |
cl N | clear breakpoint N |
tbreak file:line | one-shot breakpoint |
!stmt | execute arbitrary Python (assignments included) |
interact | drop into full Python REPL in current scope (Ctrl+D to exit) |
q | quit |
The interact command is the most powerful — you can import anything, inspect complex objects, even call methods that mutate state. Locals are read-only by default; use !x = 42 from the (Pdb) prompt to mutate.
Recipe 1: Local breakpoint
شرح للفكرة نفسها. اقرأه ببطء، فبقية الأقسام تبني عليه.
Easiest. Edit the file:
def compute(x, y):
result = some_helper(x)
breakpoint() # <-- drops into pdb here
return result + yRun the code normally. You land at the breakpoint() line with full access to locals.
Don't forget to remove breakpoint() before committing. Use git diff or a pre-commit grep:
rg -n 'breakpoint\(\)' --type pyRecipe 2: Launch a script under pdb (no source edits)
شرح للفكرة نفسها. اقرأه ببطء، فبقية الأقسام تبني عليه.
python -m pdb path/to/script.py arg1 arg2
# Lands at first line of script
(Pdb) b path/to/script.py:42
(Pdb) cRecipe 3: Debug a pytest test
قسم لحل المشكلات. ابحث فيه عن العطل الذي يشبه حالتك بدل قراءته كاملًا.
The hermes test runner and pytest both support this:
# Drop to pdb on failure (or on any raised exception):
scripts/run_tests.sh tests/path/to/test_file.py::test_name --pdb
# Drop to pdb at the START of the test:
scripts/run_tests.sh tests/path/to/test_file.py::test_name --trace
# Show locals in tracebacks without pdb:
scripts/run_tests.sh tests/path/to/test_file.py --showlocals --tb=longNote: scripts/run_tests.sh runs each test file in a captured subprocess via run_tests_parallel.py (no xdist), so interactive pdb does NOT work under the wrapper. Run pytest directly for --pdb:
source .venv/bin/activate
python -m pytest tests/foo_test.py::test_bar --pdbThis bypasses the hermetic-env guarantees — fine for debugging, but re-run under the wrapper to confirm before pushing.
Recipe 4: Post-mortem on any exception
شرح للفكرة نفسها. اقرأه ببطء، فبقية الأقسام تبني عليه.
try:
run_the_thing()
except Exception:
pdb.post_mortem(sys.exc_info()[2])Or wrap a whole script:
python -m pdb -c continue script.py
# When it crashes, pdb catches it and you're in the frame of the exceptionOr set a global hook in a repl/jupyter:
def excepthook(etype, value, tb):
import pdb; pdb.post_mortem(tb)
sys.excepthook = excepthookRecipe 5: Remote debug with debugpy (attach to running process)
قسم لحل المشكلات. ابحث فيه عن العطل الذي يشبه حالتك بدل قراءته كاملًا.
For long-lived processes: Hermes gateway, tui_gateway, a daemon, a process that's already misbehaving and can't be restarted clean.
Setup
source <hermes-agent-repo>/.venv/bin/activate
pip install debugpyPattern A: Source-edit — process waits for debugger at launch
Add near the top of the entry point (or inside the function you want to debug):
debugpy.listen(("127.0.0.1", 5678))
print("debugpy listening on 5678, waiting for client...", flush=True)
debugpy.wait_for_client()
debugpy.breakpoint() # optional: pause immediately once attachedStart the process; it blocks on wait_for_client().
Pattern B: No source edit — launch with -m debugpy
python -m debugpy --listen 127.0.0.1:5678 --wait-for-client your_script.py arg1Equivalent for module entry:
python -m debugpy --listen 127.0.0.1:5678 --wait-for-client -m your.modulePattern C: Attach to an already-running process
Needs the PID and debugpy preinstalled in the target's environment:
python -m debugpy --listen 127.0.0.1:5678 --pid <pid>
# debugpy injects itself into the process. Then attach a client as below.Some kernels/security configs block the ptrace-based injection (/proc/sys/kernel/yama/ptrace_scope). Fix with:
echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scopeConnecting a client from the terminal
The easiest terminal-side DAP client is VS Code CLI or a small script. From inside Hermes you have two practical options:
Option 1: debugpy's own CLI REPL — not an official feature, but a tiny DAP client script:
# /tmp/dap_client.py
HOST, PORT = "127.0.0.1", 5678
s = socket.create_connection((HOST, PORT))
seq = itertools.count(1)
def send(msg):
msg["seq"] = next(seq)
body = json.dumps(msg).encode()
s.sendall(f"Content-Length: {len(body)}\r\n\r\n".encode() + body)
def recv():
header = b""
while b"\r\n\r\n" not in header:
header += s.recv(1)
length = int(header.decode().split("Content-Length:")[1].split("\r\n")[0].strip())
body = b""
while len(body) < length:
body += s.recv(length - len(body))
return json.loads(body)
send({"type": "request", "command": "initialize", "arguments": {"adapterID": "python"}})
print(recv())
send({"type": "request", "command": "attach", "arguments": {}})
print(recv())
send({"type": "request", "command": "setBreakpoints",
"arguments": {"source": {"path": sys.argv[1]},
"breakpoints": [{"line": int(sys.argv[2])}]}})
print(recv())
send({"type": "request", "command": "configurationDone"})
# ... loop reading events and sending continue/stepIn/etc.This is fine for one-off automation but painful as an interactive UX.
Option 2: Attach from VS Code / Cursor / Zed — if the user has one open, they can add a launch.json:
{
"name": "Attach to Hermes",
"type": "debugpy",
"request": "attach",
"connect": { "host": "127.0.0.1", "port": 5678 },
"justMyCode": false,
"pathMappings": [
{ "localRoot": "${workspaceFolder}", "remoteRoot": "<hermes-agent-repo>" }
]
}Option 3: Ditch DAP, use remote-pdb — usually what you actually want from a terminal agent:
pip install remote-pdbIn your code:
from remote_pdb import set_trace
set_trace(host="127.0.0.1", port=4444) # blocks until connectionThen from the terminal:
nc 127.0.0.1 4444
# You get a (Pdb) prompt exactly as if debugging locally.remote-pdb is the cleanest agent-friendly choice when debugpy's DAP protocol is overkill. Use debugpy only when you actually need IDE integration.
Debugging Hermes-specific Processes
قسم لحل المشكلات. ابحث فيه عن العطل الذي يشبه حالتك بدل قراءته كاملًا.
Tests
See Recipe 3. The wrapper captures subprocess output, so run pytest directly for interactive pdb.
runagent.py / CLI — one-shot
Easiest: add breakpoint() near the suspect line, then run hermes normally. Control returns to your terminal at the pause point.
tuigateway subprocess (spawned by hermes --tui)
The gateway runs as a child of the Node TUI. Options:
A. Source-edit the gateway:
# tui_gateway/server.py near the top of serve()
debugpy.listen(("127.0.0.1", 5678))
debugpy.wait_for_client()Start hermes --tui. The TUI will appear frozen (its backend is waiting). Attach a client; execution resumes when you continue.
B. Use remote-pdb at a specific handler:
from remote_pdb import set_trace
set_trace(host="127.0.0.1", port=4444) # in the RPC handler you want to trapTrigger the matching slash command from the TUI, then nc 127.0.0.1 4444 in another terminal.
SlashWorker subprocess
Same pattern — remote-pdb with set_trace() inside the worker's exec path. The worker is persistent across slash commands, so the first trigger blocks until you connect; subsequent slash commands pass through normally unless you re-arm.
Gateway (gateway/run.py)
Long-lived. Use remote-pdb at a handler, or debugpy with --wait-for-client if you're restarting the gateway anyway.
Common Pitfalls
شرح للفكرة نفسها. اقرأه ببطء، فبقية الأقسام تبني عليه.
- pdb under a parallel/output-capturing runner silently does nothing. You won't see the prompt, the test just hangs (true of pytest-xdist and of
scripts/run_tests.sh's captured per-file subprocesses). Run pytest directly on a single file for interactive debugging.
breakpoint()in CI / non-TTY contexts hangs the process. Safe locally; never commit it. Add a pre-commit grep as a safety net.
PYTHONBREAKPOINT=0disables allbreakpoint()calls. Check the env if your breakpoint isn't hitting:
echo $PYTHONBREAKPOINTdebugpy.listenblocks only if you also callwait_for_client(). Without it, execution continues and your first breakpoint may fire before the client is attached.
- Attach to PID fails on hardened kernels.
ptrace_scope=1(Ubuntu default) allows only same-user ptrace of child processes. Workaround:echo 0 > /proc/sys/kernel/yama/ptrace_scope(needs root) or launch underdebugpyfrom the start.
- Threads.
pdbonly debugs the current thread. For multithreaded code, usedebugpy(thread-aware DAP) or setthreading.settrace()per thread.
- asyncio.
pdbworks in coroutines butawaitinside pdb requires Python 3.13+ orawaitfrominteractmode on older versions. For 3.11/3.12, useasyncio.run_coroutine_threadsafetricks or!stmt-based awaits viaasyncio.ensure_future.
scripts/run_tests.shstrips credentials and setsHOME=<tmpdir>. If your bug depends on user config or real API keys, it won't reproduce under the wrapper. Debug with rawpytestfirst to repro, then re-confirm under the wrapper.
- Forking / multiprocessing. pdb does not follow forks. Each child needs its own
breakpoint()orset_trace(). For Hermes subagents, debug one process at a time.
Verification Checklist
شرح للفكرة نفسها. اقرأه ببطء، فبقية الأقسام تبني عليه.
- [ ] After
pip install debugpy, confirm:python -c "import debugpy; print(debugpy.__version__)" - [ ] For remote debug, confirm the port is actually listening:
ss -tlnp | grep 5678 - [ ] First breakpoint actually hits (if it doesn't, you likely have
PYTHONBREAKPOINT=0, you're under a parallel/capturing runner, or execution finished before attach) - [ ]
where/wshows the expected call stack - [ ] Post-debug cleanup: no stray
breakpoint()/set_trace()in committed code
rg -n 'breakpoint\(\)|set_trace\(|debugpy\.listen' --type pyOne-Shot Recipes
شرح للفكرة نفسها. اقرأه ببطء، فبقية الأقسام تبني عليه.
"Why is this dict missing a key?"
# add above the KeyError site
breakpoint()
# then in pdb:
(Pdb) pp d
(Pdb) pp list(d.keys())
(Pdb) w # how did we get here"This test passes in isolation but fails in the suite."
scripts/run_tests.sh tests/the_test.py # confirm it fails under the isolated runner first
# For interactive debugging, or if it only fails WITH other tests:
source .venv/bin/activate
python -m pytest tests/ -x --pdb
# Now it pdb-traps at the exact failing test after state accumulated."My async handler deadlocks."
# Add at handler entryTrigger the handler. nc 127.0.0.1 4444, then w to see the suspended frame, !import asyncio; asyncio.all_tasks() to see what else is pending.
"Post-mortem on a crash in an Ink child process / subprocess."
PYTHONFAULTHANDLER=1 python -m pdb -c continue path/to/entrypoint.py
# On crash, pdb lands at the frame of the exception with full locals