Skip to content

DX-2846: Eve Extension package and demo#11

Open
CahidArda wants to merge 3 commits into
mainfrom
DX-2846
Open

DX-2846: Eve Extension package and demo#11
CahidArda wants to merge 3 commits into
mainfrom
DX-2846

Conversation

@CahidArda

@CahidArda CahidArda commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

AgentKit for eve is now one file instead of one file per tool.

// agent/extensions/agentkit.ts
import { s } from "@upstash/redis";
import agentkit from "@upstash/agentkit-eve-extension";

export default agentkit({
  userId: (ctx) => ctx.session.auth.current?.principalId ?? ctx.session.id,
  search: {
    schema: s.object({ title: s.string(), author: s.string().noTokenize(), year: s.number() }),
    indexName: "books",
  },
});

That single mount composes, under the agentkit__ namespace:

Contribution
agentkit__recall_memory / agentkit__save_memory long-term memory on Upstash Redis
agentkit__search / agentkit__search_aggregate / agentkit__search_count schema-aware Redis Search tools (RAG) — only exist when search is configured
agentkit__chat_history (hook) every message durably captured to agentkit:chat:<userId>:<sessionId> — searchable with ChatHistory
instructions fragment teaches the model when to save/recall memories

Don't want a tool? Disable its slot:

// agent/extensions/agentkit/tools/save_memory.ts
import { disableTool } from "eve/tools";
export default disableTool();

Live, from examples/eve-extension-demo (new):

user: My name is Arda and my favorite author is Ursula K. Le Guin — remember that.
      Then search the book index for her books and tell me the earliest one.

→ agentkit__save_memory {"text": "The user's name is Arda."}
→ agentkit__save_memory {"text": "The user's favorite author is Ursula K. Le Guin."}
→ agentkit__search     {"filter": {"author": {"$smart": "Ursula K. Le Guin"}}}

assistant: … the earliest is A Wizard of Earthsea (1968).

# next session
user: What is my name and what author do I like?
→ agentkit__recall_memory {"query": "user name"}
assistant: Your name is Arda, and you like Ursula K. Le Guin.
Technical details

Extension design

  • extension/extension.ts declares config with defineExtension + zod; non-JSON values (Redis
    client, userId functions, the s search schema) pass through z.custom — the mount file is
    evaluated in the runtime, so live values are fine.
  • Search tools are dynamic (defineDynamic, resolved at session.started), not static: their
    descriptions/input schemas are generated from the consumer's search.schema (per-field filter
    guides for the model), and mount config is not bound when static modules evaluate at discovery.
    One resolver per file keeps each tool in its own slot, so disableTool() overrides still target
    them individually. Unconfigured search → resolver returns null → tools don't exist.
  • Chat-history hook subscribes to message.received / message.completed and appends via core
    ChatHistory.getChat + saveChat (events per session are dispatched in order, so the
    read-modify-write doesn't race). Errors are logged and swallowed — a thrown eve hook fails the turn.
    On by default; chatHistory: false opts out.
  • userId fns are typed with eve's public SessionContext (eve/tools) — the shared base of tool
    and hook ctx, so one function serves every contribution. Default derivation:
    auth.current?.principalId ?? auth.initiator?.principalId ?? session.id, with : sanitized to _
    (eve principal ids like eve:app would violate core key-part validation).
  • Shared code lives in extension/lib/runtime.ts — extensions can have internal modules, unlike
    agent files (eve's per-file snapshot restriction), which is exactly why this packaging wins.
  • What extensions can't carry stays in @upstash/agentkit-eve: the Box sandbox backend, the
    rate-limit channel auth, and defineCachedTool.
  • Built with eve extension build; eve is a peer (^0.24.6); ships extension/ (source the
    consumer recompiles) + dist/ (mount factory and ./tools re-exports for overrides /
    toolResultFrom).

Version bumps (whole repo)

  • eve 0.17.1 → 0.24.6 everywhere, single lockfile copy. One API break: SandboxBackendHandle.dispose
    shutdown() (fires only on server shutdown, must leave the session reattachable) — the Box
    backend now does a tolerated box.pause(), mirroring eve's own Vercel backend.
  • ai beta 7.0.0-beta.178 → stable 7.0.30, exact-pinned everywhere (eve ≥0.24 declares ai
    as a peer, apps provide the single copy). Providers to stable: @ai-sdk/openai ^4.0.15,
    @ai-sdk/provider ^4.0.3, @ai-sdk/react ^4.0.33.
  • Fixed 7 pre-existing test failures the bump surfaced: tests passed colon-containing
    uniquePrefix() values as userIds, which core key-part validation rejects (unnoticed because
    Redis-less CI skips those suites). New colon-free uniqueUserId() helper in both test-supports.

Verification

  • pnpm lint / typecheck / build / test all green (69 tests against real Upstash Redis).
  • All three example apps build; eve-extension-demo exercised live end-to-end (real Redis + OpenAI):
    memory writes, schema-aware $smart search, transcript capture, and cross-session recall verified
    in Redis.

@linear-code

linear-code Bot commented Jul 17, 2026

Copy link
Copy Markdown

DX-2846

eve 0.24.x peers ai ^7.0.26, so the whole workspace moves off 7.0.0-beta.178
to a single exact-pinned ai@7.0.30 (providers to stable ^4). Breaking for the
eve adapter: eve >=0.24.0 renamed SandboxBackendHandle.dispose to shutdown()
(fires only on server shutdown, session must stay reattachable) - the Box
backend now does a tolerated box.pause().

Also fixes 7 pre-existing test failures the bump surfaced: tests passed
colon-containing uniquePrefix() values as userIds, which core key-part
validation rejects (unnoticed because Redis-less CI skips those suites).
New colon-free uniqueUserId() helper in both test-supports.
AgentKit as a mountable eve extension (eve >=0.24): one file in
agent/extensions/ composes memory tools, Redis Search tools, a durable
chat-history hook, and a memory instructions fragment under one namespace.

- search/search_aggregate/search_count are dynamic tools (session.started
  resolvers): their schema-derived descriptions need the runtime-bound mount
  config, and an unconfigured mount resolves to no tools instead of erroring
- chat_history hook appends message.received/message.completed transcripts
  to core ChatHistory (agentkit:chat:<userId>:<sessionId>); on by default,
  chatHistory: false opts out; errors swallowed so a write never fails a turn
- userId: string or (ctx: SessionContext) => string (eve's public base of
  tool + hook ctx); defaults to principal -> session id, ':' sanitized
- unwanted slots are dropped via directory mount + disableTool()
- examples/eve-extension-demo: minimal scaffold mounting the extension,
  verified live end to end (memory, $smart search, capture, recall)
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.

1 participant