Skip to content

Commit 2bb9f3f

Browse files
committed
chore(integrations): add _parity tooling + copilotkit-demo-parity skill
Introduce machinery for keeping examples/integrations/* demos aligned to a single north-star (langgraph-python). Built first so the upcoming langgraph-js and langgraph-fastapi alignment PRs have a mechanical baseline to work against instead of manual copy-paste. - examples/integrations/_parity/manifest.json declares verbatim files, tracked package.json keys, and expected agent surface (tool names, state keys) per instance plus allowed-divergence lists. - _parity/sync.ts copies verbatim files + rewrites tracked package.json keys from north-star to a target instance. Dry-run supported. - _parity/verify.ts diffs each instance vs north-star and exits non-zero on unexpected drift. Checks verbatim content, tracked keys, canonical prompt equality, and agent-surface grep-level presence. - Canonical prompt at _parity/canonical/PROMPT.md — synced into each instance's agent/PROMPT.md on parity:sync. - Root package.json: pnpm parity:sync, parity:verify, parity:check. - CI: .github/workflows/integrations_parity.yml runs parity:check on PRs touching examples/integrations/**. - Skill: .claude/skills/copilotkit-demo-parity/SKILL.md teaches agents how to drive sync/verify and handle manual-merge zones (agent code, api route, Dockerfile). Does NOT touch the existing instance demos yet. Those alignment commits follow in the same PR.
1 parent 06785a0 commit 2bb9f3f

11 files changed

Lines changed: 1408 additions & 1 deletion

File tree

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
---
2+
name: copilotkit-demo-parity
3+
description: Keeps examples/integrations/* demos aligned to the north-star (langgraph-python). Use when the user says "sync demos", "sync integrations", "port to north-star", "align integration demos", "parity check", or when working inside examples/integrations/ and tracked files diverge. Drives the pnpm parity:sync / parity:verify commands and handles the manual-merge zones (agent code, api route, Dockerfile).
4+
---
5+
6+
# CopilotKit demo parity
7+
8+
Keeps the three (soon more) integration demos under `examples/integrations/`
9+
aligned. `langgraph-python` is north-star; every other entry is an instance
10+
that tracks it via `examples/integrations/_parity/manifest.json`.
11+
12+
## When this skill fires
13+
14+
- User says "sync demos", "align integrations", "port to north-star",
15+
"parity check", "run parity sync".
16+
- User edits a file under `examples/integrations/langgraph-python/**` that is
17+
listed in `manifest.json` → tracked.verbatimFiles.
18+
- User is about to add a new integration under `examples/integrations/`.
19+
- `pnpm parity:verify` is failing locally or in CI.
20+
21+
## Ground truth
22+
23+
Read these first before making any parity decision — they override
24+
anything memorized:
25+
26+
- `examples/integrations/_parity/manifest.json` — what is tracked, what is
27+
allowed to diverge, per-instance overrides.
28+
- `examples/integrations/_parity/canonical/PROMPT.md` — the canonical
29+
system prompt. Every instance's `agent/PROMPT.md` must equal this.
30+
- `examples/integrations/_parity/README.md` — human-facing how-to.
31+
32+
## Procedure: sync from north-star to instance(s)
33+
34+
Use when north-star changed and instances need to catch up, OR when a new
35+
instance is being bootstrapped.
36+
37+
1. **Confirm the north-star change is intentional.** If the user changed
38+
a file under `langgraph-python/`, ask whether it should propagate.
39+
Changes to `agent/` are never auto-propagated.
40+
2. **Dry-run first.**
41+
```bash
42+
pnpm parity:sync --target=<instance> --dry-run
43+
```
44+
Read the report: file count, rewritten package.json keys, prompt update.
45+
3. **Apply.**
46+
```bash
47+
pnpm parity:sync --target=<instance>
48+
# or --all to sync every instance
49+
```
50+
4. **Resolve manual-merge zones.** Sync does NOT touch:
51+
- `agent/**` — port agent code by hand. See `tracked.agentSurface.toolNames`
52+
and `stateKeys` in `manifest.json` for what must be present.
53+
- `src/app/api/copilotkit/**` — api route differs across instances
54+
(north-star=LangGraphAgent, Docker instances=HttpAgent).
55+
- `Dockerfile`, `docker/Dockerfile.agent`, `serve.py`, `scripts/**`
56+
language-specific. Each instance keeps its own.
57+
5. **Verify.**
58+
```bash
59+
pnpm parity:verify --target=<instance>
60+
```
61+
Exit 0 = green. Exit 1 = unresolved drift; fix until green.
62+
63+
## Procedure: verify (CI-equivalent)
64+
65+
```bash
66+
pnpm parity:verify # all instances
67+
pnpm parity:verify --target=<instance>
68+
pnpm parity:verify --json # machine-readable
69+
```
70+
71+
Output kinds:
72+
73+
- `verbatim-file` — byte-compare against north-star.
74+
- `package-json` — tracked key mismatch vs expected value.
75+
- `prompt``agent/PROMPT.md` missing or differs from canonical.
76+
- `agent-tool` — tool name from manifest not found in instance agent
77+
source. Usually means a tool was renamed or dropped; update manifest
78+
OR port the tool.
79+
- `agent-state` — state key from manifest not found (warn only).
80+
81+
## Procedure: add a new instance
82+
83+
1. Create the demo dir under `examples/integrations/<name>/` with the
84+
usual Next.js frontend root and an `agent/` subdir.
85+
2. Add an entry to `manifest.json``instances`:
86+
```json
87+
"<name>": {
88+
"role": "instance",
89+
"agent": { "language": "...", "runtime": "..." },
90+
"allowedDivergence": [
91+
"agent/**",
92+
"src/app/api/copilotkit/**",
93+
"Dockerfile",
94+
"docker/Dockerfile.agent",
95+
"serve.py",
96+
"scripts/**"
97+
],
98+
"packageJsonOverrides": {
99+
"scripts.dev:agent": "<invocation>",
100+
"scripts.install:agent": "<install>"
101+
}
102+
}
103+
```
104+
3. `pnpm parity:sync --target=<name>`
105+
4. Port agent code (tool implementations, state schema, prompt loading).
106+
Agent must read prompt from `agent/PROMPT.md` at startup.
107+
5. `pnpm parity:verify --target=<name>` until green.
108+
109+
## Procedure: change the canonical prompt
110+
111+
1. Edit `examples/integrations/_parity/canonical/PROMPT.md`.
112+
2. `pnpm parity:sync --all` — writes the new prompt to every instance's
113+
`agent/PROMPT.md`.
114+
3. If the prompt change requires new tools or state keys, update
115+
`manifest.json``tracked.agentSurface` to match, then port each
116+
instance's agent.
117+
118+
## Procedure: change the tracked surface
119+
120+
To track a new file or key:
121+
1. Edit `manifest.json``tracked.verbatimFiles` or `tracked.packageJsonPaths`.
122+
2. Run `pnpm parity:verify` — instances will flag as drifted until synced.
123+
3. `pnpm parity:sync --all` to apply.
124+
125+
To stop tracking something, remove it from `manifest.json`. The verifier
126+
and sync stop touching it. Any drift that already exists stays.
127+
128+
## Red flags
129+
130+
| Signal | What it means | Do instead |
131+
|--------|---------------|------------|
132+
| "Just copy the file manually" | Bypasses the manifest | Add to `tracked.verbatimFiles` then sync |
133+
| "Add a try/catch so verify doesn't fail" | Silencing drift | Resolve the drift or declare divergence |
134+
| "Move this into `allowedDivergence` to unblock" | Scope creep | Only add to divergence with explicit reason |
135+
| `pnpm parity:sync` on north-star directly | Overwrites canonical | Sync is instance-only; script refuses |
136+
| Agent code copied between languages | Doesn't work | Port by hand; `tracked.agentSurface` names what |
137+
138+
## Related skills
139+
140+
- `copilotkit-integrations` — framework-specific wiring (LangGraph, CrewAI,
141+
Mastra, etc.) when bootstrapping a new instance.
142+
- `copilotkit-dev-workflow` — monorepo conventions, Nx, commit format.
143+
- `docker-ci-safety` — Dockerfile safety when editing per-instance
144+
container builds.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: "Integrations: Parity Check"
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- "examples/integrations/**"
7+
- ".github/workflows/integrations_parity.yml"
8+
push:
9+
branches: [main]
10+
paths:
11+
- "examples/integrations/**"
12+
- ".github/workflows/integrations_parity.yml"
13+
14+
permissions:
15+
contents: read
16+
17+
concurrency:
18+
group: ${{ github.workflow }}-${{ github.ref }}
19+
cancel-in-progress: true
20+
21+
jobs:
22+
parity-check:
23+
runs-on: ubuntu-latest
24+
timeout-minutes: 5
25+
steps:
26+
- uses: actions/checkout@v4
27+
28+
- name: Set up Node.js
29+
uses: actions/setup-node@v4
30+
with:
31+
node-version: "22"
32+
33+
- name: Install pnpm
34+
uses: pnpm/action-setup@v4
35+
with:
36+
version: 10.13.1
37+
run_install: false
38+
39+
- name: Install root dev dependencies (tsx)
40+
run: pnpm install --frozen-lockfile --ignore-scripts --filter=.
41+
42+
- name: Verify integration-demo parity
43+
run: |
44+
echo "Checking examples/integrations/* against north-star..."
45+
echo "If this fails, run: pnpm parity:sync --target=<instance>"
46+
echo "and resolve agent-surface drift manually (see _parity/README.md)."
47+
pnpm parity:check
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# `_parity/` — integration-demo parity tooling
2+
3+
Keeps `examples/integrations/*` demos aligned to a single north-star so
4+
drift doesn't pile up as the canonical demo evolves.
5+
6+
**North-star (v1):** `langgraph-python` — the richest demo (todos state, 5
7+
tools, polished prompt, full frontend canvas). Every other integration demo
8+
should track it.
9+
10+
## What gets tracked
11+
12+
Declared in [`manifest.json`](./manifest.json):
13+
14+
- **verbatim files** — copied byte-for-byte from north-star to each instance
15+
(frontend components, hooks, lib, public assets, shared Docker files,
16+
`docker-compose.test.yml`, `entrypoint.sh`, `postcss.config.mjs`, etc.)
17+
- **package.json keys** — tracked dependency versions and script names
18+
(`@copilotkit/*`, `next`, `react`, shared dev scripts). Per-instance
19+
overrides in manifest (`packageJsonOverrides`) win where the instance
20+
legitimately differs (e.g. `dev:agent` runs `npm install` in JS but
21+
`uv sync` in FastAPI).
22+
- **canonical prompt**`_parity/canonical/PROMPT.md`. Copied into each
23+
instance's `agent/PROMPT.md` on sync. Agents read this file at startup.
24+
- **agent surface** — tool names + state keys expected to appear in each
25+
instance's agent source. Grep-level check — doesn't validate call-site
26+
correctness, that's the aimock fixture tests' job.
27+
28+
## What doesn't get tracked (allowed divergence)
29+
30+
Per-instance `allowedDivergence` list in `manifest.json`:
31+
32+
- `agent/**` — agents are written in different languages/runtimes (Python
33+
create_agent, TS StateGraph, Python StateGraph+FastAPI). Human-authored.
34+
- `src/app/api/copilotkit/**` — north-star uses `LangGraphAgent`, Docker
35+
instances use `HttpAgent`. Different routes.
36+
- `Dockerfile`, `docker/Dockerfile.agent`, `serve.py`, `scripts/**`
37+
language-specific build/run tooling.
38+
39+
Anything outside both `tracked` and `allowedDivergence` is "no-op" — the
40+
verifier neither checks nor touches it.
41+
42+
## Commands
43+
44+
From the repo root:
45+
46+
```bash
47+
# Sync a single instance to north-star (copies verbatim files, rewrites
48+
# package.json keys, writes canonical prompt to agent/PROMPT.md)
49+
pnpm parity:sync --target=langgraph-js
50+
51+
# Dry-run: show what would change without writing
52+
pnpm parity:sync --target=langgraph-js --dry-run
53+
54+
# Sync every non-north-star instance
55+
pnpm parity:sync --all
56+
57+
# Verify — exits non-zero on unexpected drift
58+
pnpm parity:verify
59+
pnpm parity:verify --target=langgraph-js
60+
61+
# CI invocation (same as verify, no color)
62+
pnpm parity:check
63+
```
64+
65+
## Typical workflows
66+
67+
### North-star changed — sync instances
68+
69+
```bash
70+
pnpm parity:sync --all
71+
pnpm parity:verify
72+
# fix any agent-surface drift manually in the relevant agent/src/ files
73+
git add . && git commit
74+
```
75+
76+
### Adding a new instance
77+
78+
1. Create the new demo under `examples/integrations/<name>/` with a
79+
Next.js frontend at the root and an `agent/` dir.
80+
2. Add an entry to `manifest.json` under `instances`:
81+
```json
82+
"new-demo": {
83+
"role": "instance",
84+
"agent": { "language": "python", "runtime": "..." },
85+
"allowedDivergence": ["agent/**", "src/app/api/copilotkit/**",
86+
"Dockerfile", "docker/Dockerfile.agent",
87+
"serve.py", "scripts/**"],
88+
"packageJsonOverrides": { "scripts.dev:agent": "..." }
89+
}
90+
```
91+
3. Run `pnpm parity:sync --target=new-demo`.
92+
4. Hand-port the agent code (tools, state, prompt loading) — manifest
93+
`tracked.agentSurface` tells you what tool names and state keys are
94+
required.
95+
5. Run `pnpm parity:verify --target=new-demo` until green.
96+
97+
### North-star's agent surface changed
98+
99+
Edit `manifest.json``tracked.agentSurface.toolNames` / `stateKeys`.
100+
The verifier will then flag every instance that hasn't caught up.
101+
102+
### Canonical prompt changed
103+
104+
Edit `_parity/canonical/PROMPT.md`. Run `pnpm parity:sync --all` — the new
105+
prompt lands at each instance's `agent/PROMPT.md`. No other changes needed
106+
if agents already read from that file.
107+
108+
## CI
109+
110+
`.github/workflows/integrations_parity.yml` runs `pnpm parity:check` on
111+
every PR that touches `examples/integrations/**`. Failures link the
112+
contributor back to this README.
113+
114+
## Design notes
115+
116+
- **Declarative, not prescriptive.** The manifest says _what_ is tracked;
117+
the scripts just walk it. Adding a new tracked file = one line change,
118+
not a code change.
119+
- **Allowed-divergence is explicit, not implicit.** Everything is either
120+
tracked, declared-divergent, or ignored — no silent "probably different"
121+
state.
122+
- **Agent surface is grep-level, on purpose.** A real AST check would be
123+
three times the code and still miss semantic drift. The existing aimock
124+
fixture integration tests (under `fixtures/default.json` per instance)
125+
are the real correctness check; this is the "did someone rip out
126+
`manage_todos`" safety net.
127+
- **North-star is read-only to the scripts.** `sync.ts` refuses to write
128+
into the north-star directory even if asked. Verifier never touches
129+
anything.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
You are a polished, professional demo assistant. Keep responses to 1-2 sentences.
2+
3+
Tool guidance:
4+
5+
- Flights: call search_flights to show flight cards with a pre-built schema.
6+
- Dashboards & rich UI: call generate_a2ui to create dashboard UIs with metrics,
7+
charts, tables, and cards. It handles rendering automatically.
8+
- Charts: call query_data first, then render with the chart component.
9+
- Todos: enable app mode first, then manage todos.
10+
- A2UI actions: when you see a log_a2ui_event result (e.g. "view_details"),
11+
respond with a brief confirmation. The UI already updated on the frontend.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { createHash } from "node:crypto";
2+
import { readFileSync, statSync } from "node:fs";
3+
4+
export function sha256(buf: Buffer | string): string {
5+
return createHash("sha256").update(buf).digest("hex");
6+
}
7+
8+
export function fileSha256(path: string): string {
9+
return sha256(readFileSync(path));
10+
}
11+
12+
export function fileExists(path: string): boolean {
13+
try {
14+
statSync(path);
15+
return true;
16+
} catch {
17+
return false;
18+
}
19+
}
20+
21+
export function getByPath(obj: unknown, path: string): unknown {
22+
const parts = path.split(".");
23+
let cur: unknown = obj;
24+
for (const part of parts) {
25+
if (cur == null || typeof cur !== "object") return undefined;
26+
cur = (cur as Record<string, unknown>)[part];
27+
}
28+
return cur;
29+
}
30+
31+
export function setByPath(
32+
obj: Record<string, unknown>,
33+
path: string,
34+
value: unknown,
35+
): void {
36+
const parts = path.split(".");
37+
let cur: Record<string, unknown> = obj;
38+
for (let i = 0; i < parts.length - 1; i++) {
39+
const key = parts[i]!;
40+
const next = cur[key];
41+
if (next == null || typeof next !== "object" || Array.isArray(next)) {
42+
cur[key] = {};
43+
}
44+
cur = cur[key] as Record<string, unknown>;
45+
}
46+
cur[parts[parts.length - 1]!] = value;
47+
}

0 commit comments

Comments
 (0)