Skip to content

[pull] main from CopilotKit:main - #427

Merged
pull[bot] merged 8 commits into
TheTechOddBug:mainfrom
CopilotKit:main
Jul 20, 2026
Merged

[pull] main from CopilotKit:main#427
pull[bot] merged 8 commits into
TheTechOddBug:mainfrom
CopilotKit:main

Conversation

@pull

@pull pull Bot commented Jul 20, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

QA Agent and others added 8 commits July 9, 2026 08:49
…tool-based HITL

- Rewrite interrupt-flow.mdx to explicitly state Mastra lacks native interrupt support
- Add clear warning callout and comparison table showing tool-based as the working approach
- Update index.mdx to mark interrupt-based as 'Not Supported' and tool-based as 'Supported'
- Provide concrete migration path with useHumanInTheLoop examples
- Fixes FAC-64: prevents users from following broken interrupt examples
…target the real agent (#5000)

`useAgent` always returns a fully-constructed `AbstractAgent`: a provisional
stand-in while the runtime is still connecting (or in an error state), swapped
for the real agent once the `/info` sync resolves. The returned type claimed
`agent` was always the real agent, giving consumers no way to tell the two
apart — so one-time subscriptions (e.g. `onRunFinalized`) registered during the
provisional window landed on the placeholder and missed events until the effect
re-ran after the swap.

Add an `isReady` flag to the return value: `false` while the agent is
provisional, `true` once the real (or locally-registered) agent is bound.
Additive and backward compatible.

Also fix the docs' "Event Subscription" example, which used an empty
`useEffect` dependency array and therefore never re-subscribed when the agent
reference changed.

Note: the original crash from #5000 ("Cannot read properties of undefined
(reading 'subscribers')") no longer reproduces on `main` — the provisional-agent
work (#5533/#5635) guarantees a fully-constructed agent, so `subscribe()` is
always safe. The added tests lock in that no-crash behavior and cover the new
`isReady` transition.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ash)

The agno starter's Dockerfile installs Python deps via 'uv pip install --system -e .', resolving from pyproject.toml (not uv.lock). agno>=1.7.8 now resolves to 2.7.4, which dropped python-multipart as a hard dependency. AgentOS registers a FastAPI form-data route, so the app raised 'RuntimeError: Form data requires python-multipart' at import time, crash-looping starter-agno. Pin python-multipart>=0.0.20 directly so it installs regardless of agno's transitive deps.
…tool-based HITL (#5895)

## Summary

Fixes FAC-64: Mastra Interrupts docs example fails on missing agentId
and suspendPayload guard

This PR rewrites the Mastra interrupt documentation to correctly reflect
that **Mastra does not support native interrupt flow**. The framework
lacks LangGraph-style `interrupt()` primitives and does not emit AG-UI
interrupt events.

## Changes

### 📝 Documentation Updates

1. **`interrupt-flow.mdx`**: Completely rewritten to:
- Add prominent warning callout that Mastra doesn't support interrupts
   - Explain why the interrupt pattern doesn't work with Mastra
   - Provide working alternative using `useHumanInTheLoop`
- Include comparison table between interrupt-based and tool-based
approaches
   - Redirect users to the tool-based HITL guide

2. **`index.mdx`**: Updated to:
   - Mark interrupt-based approach as "Not Supported"
   - Mark tool-based approach as "Supported" (the working pattern)
   - Reorder cards to prioritize the working approach

## Rationale

The original docs documented `useInterrupt` with examples that would:
- Fail with "Agent 'default' not found" (missing `agentId` parameter)
- Fail with "Cannot destructure property 'action'" (incorrect payload
access)
- Silently fail (hook listens for events Mastra never emits)

Research revealed:
- `showcase/integrations/mastra/manifest.yaml` explicitly lists
`gen-ui-interrupt` under `not_supported_features`
- The actual working demo uses `useHumanInTheLoop`, not `useInterrupt`
- Comments in the code confirm: "This framework has no LangGraph-style
`interrupt()` primitive"

## Migration Path

Users following the old docs can now:
1. See clear warning that interrupts aren't supported
2. Learn the correct `useHumanInTheLoop` pattern
3. Follow link to complete tool-based HITL guide with working examples

## Testing

- ✅ Documentation changes only (no runtime code affected)
- ✅ Verified redirect links work correctly
- ✅ Checked against actual working implementation in
`showcase/integrations/mastra/src/app/demos/gen-ui-interrupt/page.tsx`

## Related

- Linear: FAC-64
- QA Report: Documented three specific runtime errors from the broken
examples
- Research: Identified Option B (rewrite for useHumanInTheLoop) as the
correct approach
…ess/harness-workers)

The aimock-wiring drift probe excluded `harness` and `harness-workers`,
so when their aimock pointers drifted to the billed PUBLIC
`*.up.railway.app` host instead of `showcase-aimock.railway.internal:4010`
the probe never flagged it (~$657/mo egress on staging).

Un-exclude the harness fleet and verify it as aimock consumers via a new
AIMOCK_CONSUMER_SERVICES set. Consumers use HARNESS_FLEET_CANDIDATE_ENV_VARS
(the standard OPENAI/ANTHROPIC/GEMINI candidates plus AIMOCK_URL), because
`harness` exposes ONLY AIMOCK_URL as its aimock pointer. AIMOCK_URL is scoped
to the harness-fleet path only, so a regular backend's stray AIMOCK_URL can't
mask a missing real base-URL pointer. Pure-infra services (aimock/shell/
dashboard/docs/dojo/pocketbase/webhooks) stay excluded.
…ash) (#6061)

## The crash

`starter-agno` (Railway service, built from
`examples/integrations/agno`) was crash-looping at import time:

```
RuntimeError: Form data requires "python-multipart" to be installed.
  fastapi/dependencies/utils.py  ensure_multipart_is_installed()
  agno/os/routers/agents/router.py  get_agent_router(...)
  agno/os/app.py  _add_built_in_routes(...)
```

The app dies during startup import — before any network/LLM activity.

## Root cause

The starter's top-level `Dockerfile` installs Python deps with:

```dockerfile
RUN cd agent && uv pip install --system -e .
```

`uv pip install -e .` resolves from `pyproject.toml` and **does not
consult `uv.lock`**. The pyproject pinned `agno>=1.7.8`, which now
resolves to **agno 2.7.4**. agno **dropped `python-multipart` as a hard
dependency** in the 2.7.x line (it was still a hard dep in the locked
2.3.3, and in 2.6.19). agno's `AgentOS` registers a FastAPI form-data
route, and FastAPI's `ensure_multipart_is_installed()` raises at
route-registration (import) time when the package is absent — so the
agent never starts.

The pinned `uv.lock` (agno 2.3.3) *did* carry `python-multipart`
transitively, which is why the lockfile looked fine while the deployed
image (which bypasses the lock) broke.

## Fix

Add `python-multipart>=0.0.20` directly to the starter agent's
`pyproject.toml` dependencies (and refresh `uv.lock`) so it installs
regardless of agno's transitive-dependency changes.

## Red → Green proof

Reproduced against the **real deployed install path** (`uv pip install
-e .` from pyproject, lock bypassed) in a clean Python 3.12 venv.

### RED (main, before fix)
```
agno resolved: 2.7.4   fastapi: 0.139.2   python-multipart: ABSENT
$ python -c "import main"
RuntimeError: Form data requires "python-multipart" to be installed.
You can install "python-multipart" with:
pip install python-multipart
```

### GREEN (after fix)
```
agno resolved: 2.7.4   fastapi: 0.139.2   python-multipart: 0.0.32  (now installed)
$ python -c "import main"
IMPORT OK - AgentOS app built, no RuntimeError

$ uvicorn main:app ...
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8123
$ curl /health  ->  {"status":"ok",...}
```

## Docker build (local, real starter image)

Built the actual starter image from the top-level `Dockerfile` (the
deployed path using `uv pip install --system -e .`) with the local
BuildKit builder, then ran it:

```
docker buildx build -f Dockerfile -t agno-fix-test .   ->  EXIT 0
docker run ... agno-fix-test
  agent /health -> 200 {"status":"ok",...}
  agent log: "Application startup complete." / "Uvicorn running on http://0.0.0.0:8000"
  RuntimeError count in container logs: 0
  in-image check: python-multipart present: 0.0.32
```

## Other agno variants

`showcase/integrations/agno` uses a **separate** dep file
(`requirements.txt`, not this pyproject/uv.lock) and pins
`agno==2.6.19`, which still carries `python-multipart` transitively — so
it is **not currently broken**, but it is fragile (a bump past 2.7.x
would hit the same crash). No shared dep file, so it is intentionally
out of scope here; recommend a follow-up to add an explicit
`python-multipart` pin there too.
…ess/harness-workers) (#6062)

## The incident this prevents

The showcase pays egress whenever a service reaches aimock over the
PUBLIC `*.up.railway.app` host instead of the free
`showcase-aimock.railway.internal:4010`. On STAGING, `harness-workers`
(the 6-replica probe fleet) had `OPENAI_BASE_URL` / `ANTHROPIC_BASE_URL`
/ `AIMOCK_URL` pointing at PUBLIC aimock, and `harness` had a public
`AIMOCK_URL` — together ~$657/mo of egress. The aimock-wiring drift
probe **never flagged this** because both services were in
`EXCLUDE_SERVICES`. The live vars are already fixed; this makes the
probe cover the class so it can't silently regress.

## Design choice + justification

Two facts constrained the fix:

1. `harness` / `harness-workers` were excluded (`EXCLUDE_SERVICES`) →
skipped entirely, so a naive fix must un-exclude them.
2. `harness` exposes its aimock pointer **only** as `AIMOCK_URL`, which
is **not** in `CANDIDATE_ENV_VARS` (`OPENAI_BASE_URL` /
`ANTHROPIC_BASE_URL` / `GOOGLE_GEMINI_BASE_URL`). So merely un-excluding
`harness` would leave it all-missing → unwired forever, even when
correctly wired.

Chosen approach — a dedicated **aimock-consumer** class:

- Remove `harness` / `harness-workers` from `EXCLUDE_SERVICES`.
- Add `AIMOCK_CONSUMER_SERVICES = { harness, harness-workers }` +
`isAimockConsumer(name)` (mirrors `isExcluded`: matches bare and legacy
`showcase-`-prefixed forms).
- Add `HARNESS_FLEET_CANDIDATE_ENV_VARS = [...CANDIDATE_ENV_VARS,
"AIMOCK_URL"]`; `pointsAtAimock` takes a `candidateVars` param (defaults
to the standard set). In the run loop, consumers use the extended set,
everything else the standard set.

This is the minimal correct surface: it catches `harness` via
`AIMOCK_URL`, catches `harness-workers` via any of
OPENAI/ANTHROPIC/AIMOCK_URL, and leaves the verdict precedence (match >
confirmed-mismatch > sealed > missing) untouched.

### Why `AIMOCK_URL` is scoped to the harness-fleet path (and safe)

Adding `AIMOCK_URL` to the **global** candidate set is not safe: a
regular demo backend that happens to expose `AIMOCK_URL` (pointed
anywhere) could then count as "wired" and **mask a missing real
`OPENAI_BASE_URL`/etc pointer**, hiding genuine drift. Scoping
`AIMOCK_URL` to `HARNESS_FLEET_CANDIDATE_ENV_VARS` means only the two
harness-fleet services consult it. A regression guard test (`does NOT
consult AIMOCK_URL for non-harness services`) locks this in. Pure-infra
services with no aimock pointer
(aimock/shell/dashboard/docs/dojo/pocketbase/webhooks) stay excluded and
never go red.

## Red → Green proof

Tests added in `aimock-wiring.test.ts`. RED was captured against the
**unchanged** probe (new tests only, source untouched); GREEN after the
fix + updating the 4 existing tests that asserted the old
harness-excluded behavior.

**RED** (new behavior tests fail on current code — harness fleet
excluded, so the incident is not flagged):

```
 FAIL  aimock-wiring.test.ts > flags the harness fleet when its aimock pointers are on the PUBLIC host (egress drift)
   AssertionError: expected 'green' to be 'red'
 FAIL  aimock-wiring.test.ts > greens the harness fleet when its aimock pointers are on the PRIVATE internal host
   AssertionError: expected [] to deeply equal [ 'harness', 'harness-workers' ]
 FAIL  aimock-wiring.test.ts > verifies `harness` via its only aimock pointer, AIMOCK_URL
   AssertionError: expected 'green' to be 'red'

 Test Files  1 failed (1)
      Tests  3 failed | 44 passed (47)
```

**GREEN** (after the fix):

```
 Test Files  1 passed (1)
      Tests  47 passed (47)
```

Test coverage added:
- `flags the harness fleet when its aimock pointers are on the PUBLIC
host (egress drift)` — the exact incident → red.
- `greens the harness fleet when its aimock pointers are on the PRIVATE
internal host` — positive path → wired/green.
- `verifies harness via its only aimock pointer, AIMOCK_URL` —
public→red, internal→green (locks the `AIMOCK_URL`-candidate path).
- `does NOT flag pure-infra services with no aimock pointer` — guard:
shell/dashboard/etc stay excluded.
- `does NOT consult AIMOCK_URL for non-harness services` — guard:
`AIMOCK_URL` is not global.

## Local quality

- oxfmt (formatter) — clean on both files
- oxlint — 0 warnings, 0 errors
- `tsc --noEmit` (typecheck) — clean
- `tsc -p tsconfig.build.json` (build) — clean
- Full harness suite: only the pre-existing unrelated failures remain
(`d0-gone-predicate.test.ts`, `d5-mapping-drift.test.ts`, and
`cvdiag/staged-ts-scrub-parity.test.ts`) — all confirmed failing
identically on the untouched baseline (verified via `git stash`). This
PR adds **no** new failures.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01QCLub2Vb5Y56cPttSzkip1
…iptions (#5000) (#6041)

## Summary

Closes #5000.

`useAgent` (v2) always returns a **fully-constructed** `AbstractAgent` —
a *provisional* stand-in while the runtime is still connecting (or in an
error state), swapped for the real agent once the `/info` sync resolves.
The return type claimed `agent` was always the real agent, so consumers
had **no way to tell the provisional instance from the real one**.
One-time subscriptions registered during the provisional window (e.g.
`onRunFinalized`) landed on the placeholder and missed events until the
effect re-ran after the swap.

This PR adds an **`isReady`** flag to the return value:

- `false` — `agent` is provisional (runtime connecting / error)
- `true` — `agent` is the real, runtime-synced (or locally-registered)
instance

This is exactly the API the issue requests in its *Expected Behavior*.
It is **additive and backward compatible** — existing `const { agent } =
useAgent()` callers are unaffected.

```tsx
const { agent, isReady } = useAgent({ agentId });

useEffect(() => {
  if (!isReady) return; // only subscribe once the real agent is bound
  const sub = agent.subscribe({ onRunFinalized: (p) => console.log(p) });
  return () => sub.unsubscribe();
}, [agent, isReady]);
```

## On the original crash

The crash reported in #5000 — `Cannot read properties of undefined
(reading 'subscribers')` at `AbstractAgent.subscribe` — **no longer
reproduces on `main`**. The provisional-agent work landed for #5533 /
#5635 now guarantees `useAgent` always returns a fully-constructed
`AbstractAgent`, so `subscribe()` is always safe to call. The added
tests lock in that no-crash behavior. What remained unaddressed was the
missing readiness signal, which this PR provides.

## Changes

- **`packages/react-core/src/v2/hooks/use-agent.tsx`** — `useMemo` now
returns `{ agent, isReady }`; real agent → `isReady: true`, provisional
paths → `isReady: false`. Documented with JSDoc.
- **`use-agent-subscribe-ready.test.tsx`** (new) — regression + behavior
coverage: `subscribe()` does not throw while connecting (effect +
during-render), `isReady` transitions `false → true` on sync and swaps
the instance, local agent is ready immediately.
- **`showcase/shell-docs/.../hooks/useAgent.mdx`** — signature +
return-value docs updated; the *Event Subscription* example fixed (it
used an empty `useEffect` dep array and never re-subscribed when the
agent reference changed).

## Testing

- New test file: 4/4 pass.
- Full `react-core` v2 hooks suite: **35 files / 299 tests pass** (the
`useMemo` return-shape change breaks nothing).
- `tsc --noEmit` clean.

## Notes

- Scope is React only, matching the issue. The Vue `useAgent`
(`packages/vue`) is structured differently (reactive `shallowRef`,
`agent` can be `null`); happy to add matching `isReady` as a follow-up
if maintainers want cross-framework parity.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@pull pull Bot locked and limited conversation to collaborators Jul 20, 2026
@pull pull Bot added the ⤵️ pull label Jul 20, 2026
@pull
pull Bot merged commit 5595cf7 into TheTechOddBug:main Jul 20, 2026
0 of 3 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants