Skip to content

feat(examples): GitHub Copilot CLI agent (github-copilot-sdk==0.2.2)#492

Draft
elefthei wants to merge 3 commits intoAgent-Field:mainfrom
elefthei:copilot-cli-agent
Draft

feat(examples): GitHub Copilot CLI agent (github-copilot-sdk==0.2.2)#492
elefthei wants to merge 3 commits intoAgent-Field:mainfrom
elefthei:copilot-cli-agent

Conversation

@elefthei
Copy link
Copy Markdown

@elefthei elefthei commented Apr 20, 2026

Summary

Ships GitHub Copilot CLI as a first-class AgentField reasoner node, backed by the official github-copilot-sdk==0.2.2 (Public Preview). Other agents on the field can call Copilot the same way they call any other AgentField reasoner — no subprocess parsing, no bespoke protocol, full access to Copilot's event model and permission hooks.

Track A of the two-PR Copilot integration (Track B is #491, the skillkit target + doctor probes — independent of this PR).

Lives entirely under examples/python_agent_nodes/copilot_agent/:

  • copilot_session.py — thin wrapper that returns a stable CopilotRunResult dict from a single Copilot turn. Event discrimination via SessionEventType (stable enum), never isinstance on event.data — subclass names like AssistantMessageData don't exist in the PyPI build even though the repo samples reference them.
  • reasoners.py — four reasoners, ask / plan / review / run_task. Deny-by-default permission posture; run_task requires allow_tools=True and takes precedence-ordered allow_list / deny_list. Permission handler rejects with PermissionRequestResult(kind="denied", ...).
  • Session mapping — the AgentField session id is mapped to a Copilot session id via session-scoped memory (copilot_session_id key). Different AF sessions always get independent Copilot sessions; continue_session=True reuses the mapping.
  • Auth — reads COPILOT_GITHUB_TOKENGH_TOKENGITHUB_TOKEN → falls back to copilot --login user. Token never leaves the process env.
  • Isolation — default is the user's real ~/.copilot/ so skills installed by af skill install and auth from copilot --login are visible. AGENTFIELD_COPILOT_ISOLATE=1 (or isolate=True) switches to a per-node sandbox under $AGENTFIELD_HOME/copilot-home/<node_id>.
  • SDK compat smoke testtests/test_sdk_compat.py imports every SDK symbol the example uses and fails loudly if the preview SDK drifts.

No changes outside examples/python_agent_nodes/copilot_agent/ and CHANGELOG.md; no new core deps.

Testing

  • ./scripts/test-all.sh — N/A (no change to control-plane/, sdk/go/, or sdk/python/; the script does not traverse examples/).
  • Additional verification:
    • pytest tests/ in the example — 21 passed (0 skipped, 0 xfail) against the stubbed CopilotClient in conftest.py. No live Copilot subscription required.
    • tests/test_sdk_compat.py asserts CopilotClient.create_session signature, CopilotSession.send_and_wait shape, PermissionHandler.approve_all, and every SessionEventType enum value we rely on.
    • python -m py_compile clean on copilot_session.py, reasoners.py, main.py.
    • Not yet exercised against a real Copilot login in CI (requires a fine-grained PAT with "Copilot Requests" permission; classic ghp_* PATs are rejected by the backend).

Checklist

  • I updated documentation where applicable. (Example README.md covers quickstart, auth, isolation, cross-agent calls, and links to feat(skillkit,doctor): GitHub Copilot CLI target + doctor probes #491 for the skillkit half.)
  • I added or updated tests. (21 unit tests + SDK compat smoke test in tests/.)
  • I updated CHANGELOG.md — new [Unreleased] entry under ### Added.

Screenshots (if UI-related)

Not UI-related.

Related issues

Pairs with #491 (Copilot skillkit + doctor probes, independent — merge in either order). Installing #491 makes af skill install flow into ~/.copilot/skills/, visible to the session created by this example out of the box.

Track A of copilot-cli-support plan.

New example at examples/python_agent_nodes/copilot_agent/ that exposes
GitHub Copilot CLI as a first-class AgentField node via the official
github-copilot-sdk (Public Preview). Four reasoners:

- ask        — one-shot Q&A, no tools
- plan       — step-by-step plan without executing, no tools
- review     — inline diff (no tools) or file list (read-only allow-list)
- run_task   — full agent mode, requires allow_tools=True opt-in;
               allow_list beats deny_list

Structured output is a stable dict (af_session_id, copilot_session_id,
answer, transcript, tool_calls, usage, finished_reason, error) that
insulates callers from SDK preview churn.

Key design choices (post rubber-duck critique):

- Default config_dir=None: reuses the user's real ~/.copilot so that
  skills placed by the new skillkit target, stored auth, and session
  resume are all visible. Opt-in isolation via isolate=True or
  AGENTFIELD_COPILOT_ISOLATE=1 uses a per-NODE (not per-run) sandbox,
  keeping concurrent reasoner calls on the same node coherent.
- AF session id ↔ Copilot session id map kept in session-scoped memory
  under 'copilot_session_id', never reused directly.
- Deny-by-default permission handler for every reasoner except run_task
  when the caller explicitly opts in.
- Event discrimination via SessionEventType enum, never via isinstance
  on event.data — the Data dataclass is a union in v0.2.2 and subclass
  names are not exported.
- Auth forwarded from COPILOT_GITHUB_TOKEN / GH_TOKEN / GITHUB_TOKEN into
  SubprocessConfig(github_token=...).

21 unit tests (mocked CopilotClient, no Copilot subscription required in
CI) plus a tests/test_sdk_compat.py smoke test that fails loudly if the
preview SDK drifts.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


Lef Ioannidis seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

Tidies six small warts surfaced during self-review. No behaviour change;
all 21 unit tests still pass.

- pyproject.toml: drop unused pydantic>=2.0 dependency
- copilot_session.py: rename _deny_all_handler -> deny_all_handler so it
  can be imported without crossing the leading-underscore boundary; lift
  the nonlocal error_msg declaration out of the SESSION_ERROR branch to
  the top of _on_event and drop the incorrect noqa: PLW0603 (PLW0603 is
  the code for global-statement, not nonlocal); default final_event = None
  before the try block so the post-session read is well-typed even on an
  early error path
- reasoners.py: update import + call sites for the public handler name
- tests/test_copilot_session.py: drop unused monkeypatch fixture in
  test_ask_happy_path; follow handler rename
- README.md: rewrite See-also to reference PR Agent-Field#491 rather than paths that
  only exist on the skillkit branch

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants