Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions src/clayde/webhook/skills.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ def _parse_skill(path: Path) -> Skill:
"log", or "capture", write a file there. No git operations — Syncthing
handles sync.

You have a hard wall-clock budget of {timeout_s} seconds for this
entire request. If your process exceeds it, it is killed and the user gets
no result. Scope your work to fit: prefer a smaller, complete action over an
ambitious one that risks timing out. If the request is too big to finish in
time, do the most valuable part you can and say so in the JSON summary.

{skill_section}

Skills are suggestions, not constraints. Use as many as the command needs,
Expand All @@ -72,8 +78,12 @@ def _parse_skill(path: Path) -> Skill:
"""


def build_system_prompt(skills: list[Skill]) -> str:
"""Build the system prompt sent to the Claude CLI for a Pebble request."""
def build_system_prompt(skills: list[Skill], timeout_s: int = 300) -> str:
"""Build the system prompt sent to the Claude CLI for a Pebble request.

``timeout_s`` is the hard wall-clock budget enforced by the runner; it is
surfaced in the prompt so Claude can scope work to fit.
"""
if not skills:
skill_section = "Available skills: (none currently registered)"
else:
Expand All @@ -85,7 +95,9 @@ def build_system_prompt(skills: list[Skill]) -> str:
"Skill file paths:\n\n"
f"{files}"
)
return _SYSTEM_PROMPT_TEMPLATE.format(skill_section=skill_section)
return _SYSTEM_PROMPT_TEMPLATE.format(
skill_section=skill_section, timeout_s=timeout_s,
)


def build_user_prompt(text: str, timestamp: int) -> str:
Expand Down
2 changes: 1 addition & 1 deletion src/clayde/webhook/worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ async def process_job(job: PebbleJob, *, timeout_s: int, kb_path: str) -> None:

skills = discover_skills(SKILLS_ROOT)
span.set_attribute("pebble.skills_available", len(skills))
system_prompt = build_system_prompt(skills)
system_prompt = build_system_prompt(skills, timeout_s=timeout_s)
user_text = build_user_prompt(job.text, job.timestamp)

t0 = time.monotonic()
Expand Down
2 changes: 1 addition & 1 deletion tests/test_pebble_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ async def fake_invoke(**kwargs):

monkeypatch.setattr(worker_mod, "invoke_claude_pebble", fake_invoke)
monkeypatch.setattr(worker_mod, "discover_skills", lambda root=None: [])
monkeypatch.setattr(worker_mod, "build_system_prompt", lambda skills: "SYS")
monkeypatch.setattr(worker_mod, "build_system_prompt", lambda skills, timeout_s=300: "SYS")
monkeypatch.setattr(worker_mod, "build_user_prompt", lambda text, ts: text)

# Real queue + real worker_loop.
Expand Down
8 changes: 8 additions & 0 deletions tests/test_webhook_skills.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,14 @@ def test_build_system_prompt_empty_catalog():
assert "(none currently registered)" in prompt


def test_prompt_mentions_timeout_budget():
p = build_system_prompt([], timeout_s=300)
assert "300 seconds" in p
assert "budget" in p.lower()
# the runner kills on overrun — the agent must be told to scope work
assert "killed" in p.lower()


def test_build_user_prompt():
out = build_user_prompt("hello world", 1778068506)
assert "1778068506" in out
Expand Down
2 changes: 1 addition & 1 deletion tests/test_webhook_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ async def fake_send(*, title, body, success, **_):
@pytest.fixture
def fake_skills(monkeypatch):
monkeypatch.setattr(worker, "discover_skills", lambda root=None: [])
monkeypatch.setattr(worker, "build_system_prompt", lambda skills: "SYS")
monkeypatch.setattr(worker, "build_system_prompt", lambda skills, timeout_s=300: "SYS")
monkeypatch.setattr(worker, "build_user_prompt", lambda text, ts: f"USER:{text}")


Expand Down
Loading