Skip to content
Open
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
14 changes: 12 additions & 2 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,9 @@ dashboard_output = ".agentpack/learning-dashboard.html"
team_lessons_output = ".agentpack/team-lessons.md"
provider_command = ""
provider_timeout_seconds = 60
concept_provider_command = ""
concept_provider_timeout_seconds = 30
concept_provider_required = false
inject_agent_lessons = true
max_changed_files = 20
max_diff_chars_per_file = 1200
Expand All @@ -196,8 +199,15 @@ claim-level citation coverage: generated summaries, decisions, risks, tests,
lessons, cards, topics, and skill evidence should cite source files with line
anchors where available. Learning artifacts are local by default: no hosted
service is called, diffs are bounded, and secret redaction runs before diff text
is used. `provider_command` is opt-in and runs a local JSON-in/JSON-out command with the bounded report payload on
stdin.
is used. `provider_command` and `concept_provider_command` are opt-in local
JSON-in/JSON-out commands that receive the bounded report payload on stdin.

A practical You.com setup is to point `provider_command` at a small script such
as `python scripts/youcom_research_provider.py`. That script can read the
current learning report, call the You.com Research API with `YDC_API_KEY`, and
return extra `summary`, `learning_topics`, `concepts`, or `next_practice` fields
without changing the default offline flow.

Feedback-aware skill memory and practice drills are stored locally in
`skill_map_output` and `feedback_output`; shared team learning should export
only selected lessons or taxonomy files, not personal skill history. Use
Expand Down
98 changes: 98 additions & 0 deletions scripts/youcom_research_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
from __future__ import annotations

import json
import os
import sys
from typing import Any
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen


API_URL = "https://api.you.com/v1/research"
DEFAULT_EFFORT = "standard"


def build_prompt(payload: dict[str, Any]) -> str:
task = str(payload.get("task", "")).strip()
current = payload.get("current_report") or {}
source_files = current.get("source_files") or []

parts: list[str] = []
if task:
parts.append(f"Task: {task}")
if source_files:
files = ", ".join(
f'{item.get("path", "").strip()} ({", ".join(item.get("concepts", [])[:3])})'.strip()
for item in source_files
if isinstance(item, dict) and item.get("path")
)
if files:
parts.append(f"Changed files: {files}")
parts.append(
"Use live web sources to surface any relevant docs, maintainer guidance, or implementation details "
"that would help a developer work safely on this task. Keep the answer concise and practical."
)
return "\n\n".join(parts)


def enrich_with_research(payload: dict[str, Any]) -> dict[str, Any]:
api_key = os.environ.get("YDC_API_KEY", "").strip()
if not api_key:
raise RuntimeError("YDC_API_KEY environment variable is required")

research_body = json.dumps({"input": build_prompt(payload), "research_effort": DEFAULT_EFFORT}).encode("utf-8")
request = Request(
API_URL,
data=research_body,
headers={"X-API-Key": api_key, "Content-Type": "application/json"},
method="POST",
)
try:
with urlopen(request, timeout=60) as response:
raw = response.read().decode("utf-8")
except HTTPError as exc:
detail = exc.read().decode("utf-8", errors="replace") if exc.fp else str(exc)
raise RuntimeError(f"You.com Research API error {exc.code}: {detail}") from exc
except URLError as exc:
raise RuntimeError(f"You.com Research API error: {exc.reason}") from exc

data = json.loads(raw)
content = ((data.get("output") or {}).get("content") or "").strip()
sources = (data.get("output") or {}).get("sources") or []
if not content:
raise RuntimeError("You.com Research API returned no content")

summary = [content.splitlines()[0].lstrip("# ").strip() or "Live research notes"]
if sources:
summary.append(f"Live sources reviewed: {len(sources)}")

topics: list[dict[str, Any]] = []
if sources:
topics.append(
{
"title": "Live research follow-up",
"why": "Use the cited sources to confirm external implementation guidance before changing the code.",
"prompt": content[:1200],
"files": [item.get("url", "") for item in sources if isinstance(item, dict) and item.get("url")],
"concepts": ["live web research", "cited sources"],
}
)

return {
"summary": summary,
"learning_topics": topics,
"concepts": ["live web research", "cited sources"],
"next_practice": "Use the cited sources as a quick sanity check before implementing any externally documented behavior.",
}


def main() -> int:
payload = json.load(sys.stdin)
override = enrich_with_research(payload)
json.dump(override, sys.stdout, indent=2, sort_keys=True)
sys.stdout.write("\n")
return 0


if __name__ == "__main__":
raise SystemExit(main())
22 changes: 22 additions & 0 deletions tests/test_youcom_research_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from __future__ import annotations

from scripts.youcom_research_provider import build_prompt


def test_build_prompt_includes_task_and_files() -> None:
prompt = build_prompt(
{
"task": "add web-grounded learning notes",
"current_report": {
"source_files": [
{"path": "src/agentpack/commands/learn.py", "concepts": ["provider command"]},
{"path": "src/agentpack/learning/provider.py", "concepts": ["JSON command"]},
]
},
}
)

assert "Task: add web-grounded learning notes" in prompt
assert "src/agentpack/commands/learn.py (provider command)" in prompt
assert "src/agentpack/learning/provider.py (JSON command)" in prompt
assert "live web sources" in prompt