API Redesign for @honcho/ai-sdk

Context

The current integration requires too much ceremony. Before calling generateText, a developer needs to understand workspaces, sessions, peers, observation configs, middleware, and wrapLanguageModel. Competing providers (Mem0, Supermemory, Hindsight) need at most a user ID. The package hasn’t been published yet, so we can rethink the API fundamentally.

SDK Migration: @honcho-ai/core@honcho-ai/sdk

The current package depends on the deprecated @honcho-ai/core (v2.2.0) — a low-level auto-generated client targeting the v2 API. It requires a v3Fetch hack to rewrite URLs to the v3 API.

@honcho-ai/sdk (v2.0.1) is the current SDK — a DX-optimized wrapper that:

  • Natively targets the v3 API (no URL rewriting needed)
  • Has object-oriented API: peer.chat(query) instead of client.workspaces.peers.chat(wsId, peerId, { query })
  • Has SessionContext.toOpenAI(assistant) / .toAnthropic(assistant) native converters
  • Has built-in lazy peer/session creation (honcho.peer("id") handles getOrCreate)
  • Defaults workspaceId to HONCHO_WORKSPACE_ID env var
  • Has peer.message(content) helper and session.addMessages()

This migration eliminates most of the wrapper code in src/shared/context.ts.

Design: Flat Middleware Config

No session objects. One middleware function with flat config. Complexity is opt-in.

Middleware Config Shape

{
  userId: string;             // the user peer — observed, modeled (required)
  sessionId?: string;         // conversation thread — enables persistence + full context
  assistantId?: string;       // the AI peer — observer, generates (defaults to "assistant")
  persistInput?: boolean;     // also save user message on persist (default: true)
  injectHistory?: boolean;    // inject recent messages from Honcho (default: true)
  formatContext?: Function;   // custom context formatter
}

Usage Examples

const honcho = createHoncho(); // zero-config: reads HONCHO_API_KEY + HONCHO_WORKSPACE_ID from env
 
// === Simplest: inject what Honcho knows about this user (no persistence) ===
await generateText({
  model: openai('gpt-4o'),
  middleware: honcho.middleware({ userId: 'user-123' }),
  prompt: 'What should I focus on today?',
});
 
// === Add persistence: include sessionId ===
await generateText({
  model: openai('gpt-4o'),
  middleware: honcho.middleware({ userId: 'user-123', sessionId: 'chat-456' }),
  prompt: 'What should I focus on today?',
});
 
// === Add tools: let the agent reason about the user ===
await generateText({
  model: openai('gpt-4o'),
  middleware: honcho.middleware({ userId: 'user-123', sessionId: 'chat-456' }),
  tools: honcho.tools({ userId: 'user-123' }),
  maxSteps: 3,
  prompt: 'What should I focus on today?',
});
 
// === With messages array: disable history injection to avoid duplication ===
await generateText({
  model: openai('gpt-4o'),
  middleware: honcho.middleware({
    userId: 'user-123',
    sessionId: 'chat-456',
    injectHistory: false,     // developer owns conversation history
  }),
  messages: conversationHistory,
});
 
// === Multi-peer: specify who's generating ===
await generateText({
  model: openai('gpt-4o'),
  middleware: honcho.middleware({
    assistantId: 'agent-coordinator',
    userId: 'alice',
    sessionId: 'group-123',
  }),
  prompt: aliceMessage,
});
 
// === Multi-peer: different agent, same session ===
await generateText({
  model: openai('gpt-4o'),
  middleware: honcho.middleware({
    assistantId: 'agent-specialist',
    userId: 'bob',
    sessionId: 'group-123',
  }),
  prompt: bobMessage,
});
 
// === Multi-peer: developer manages input persistence ===
await honcho.send({ userId: 'alice', sessionId: 'group-123', content: aliceMessage });
await generateText({
  model: openai('gpt-4o'),
  middleware: honcho.middleware({
    assistantId: 'coordinator',
    userId: 'alice',
    sessionId: 'group-123',
    persistInput: false,
  }),
  prompt: aliceMessage,
});

Key Design Decisions

1. Flat middleware, no session object

  • No honcho.session(), no createMultiAgentSession(). One honcho.middleware() call with flat config.
  • userId = the observed peer (human user). assistantId = the observer/generator (AI). Defaults to "assistant".
  • For multi-peer, just change assistantId per generateText call.
  • Peers are created lazily with sensible defaults (userIdobserve_me: true, assistantIdobserve_me: false, observe_others: true).

2. Persistence: output always, input via flag

  • When sessionId is present, the middleware always saves the model’s output as assistantId’s message.
  • persistInput (default: true) controls whether the user’s input is also saved as userId’s message.
  • For multi-step tool loops with persistInput: true, the user message is only saved on the first step (tool continuation detection).
  • persistInput: false for cases where the developer saves messages themselves (avoids double-counting).

3. History injection via flag

  • injectHistory (default: true) controls whether recent messages from Honcho are injected into the system prompt.
  • Representation, peer card, and session summary are ALWAYS injected when available (these are Honcho metadata, not conversation history).
  • Set injectHistory: false when passing a messages array to generateText to avoid duplication.

4. honcho.send() convenience

  • One-liner to save a peer’s message to a session.
  • Used when the developer manages input persistence themselves.
await honcho.send({ userId: 'alice', sessionId: 'group-123', content: aliceMessage });

5. Sessions are optional, not auto-generated

  • Without sessionId: peer-only context (representation + card). No persistence, no assistant peer.
  • With sessionId: full session context (+ summary + messages) and message persistence.
  • Developer provides their own session/conversation/thread ID. No auto-generation.

6. workspaceId optional with env var fallback

  • createHoncho() with zero args reads HONCHO_API_KEY + HONCHO_WORKSPACE_ID from env.
  • Throws eagerly if workspace ID not found.

7. Provider-level caching

  • middleware() and tools() with the same (userId, sessionId, assistantId) share the same internal state.
  • Cache keyed by composite key inside the createHoncho() closure.
  • Peer getOrCreate calls are cached and idempotent.

8. userId / assistantId naming

  • userId is clear about which peer it maps to (the user being observed).
  • assistantId is clear about the AI generating the response.
  • Multi-peer: assistantId specifies which agent is generating, userId specifies who sent the input / who to fetch context about.

How the Middleware Works Internally

transformParams (before generation)

  1. Lazily ensure peers exist (getOrCreate with correct observation configs)
  2. If sessionId: lazily ensure session exists
  3. Fetch context:
    • Always: representation + peer card (via peers.context() or sessions.context())
    • If sessionId: session summary
    • If sessionId + injectHistory: true: recent messages
  4. Format context (custom formatContext or default XML formatter)
  5. Inject into system prompt

wrapGenerate / wrapStream (after generation)

  1. If no sessionId: no-op (no persistence)
  2. If persistInput: true and not a tool continuation: extract last user message from prompt, save as userId’s message
  3. Save model output as assistantId’s message
  4. Errors handled via configurable onError (default: console.warn)

Role mapping for injected history

When injectHistory: true, messages loaded from Honcho are labeled:

  • Messages from assistantId[assistant]
  • Messages from everyone else → [user] (with peer ID label for multi-peer)

Implementation

Phase 0: SDK Migration (package.json, src/shared/context.ts)

  • Replace @honcho-ai/core with @honcho-ai/sdk in package.json
  • Rewrite createClient() in src/shared/context.ts:
    • new Honcho({ workspaceId, apiKey }) — no v3Fetch hack
    • Remove v3Fetch function entirely
    • SDK handles HONCHO_WORKSPACE_ID and HONCHO_API_KEY env var fallbacks natively
  • The SDK provides:
    • honcho.peer(id) → lazy getOrCreate, returns Peer object with .chat(), .search(), .getContext(), .message()
    • honcho.session(id) → lazy getOrCreate, returns Session object with .context(), .addMessages(), .addPeers()
    • session.context({ peer_perspective, peer_target, summary, tokens }) → returns SessionContext with .toOpenAI(assistant) / .toAnthropic(assistant)
    • peer.chat(query, { target?, sessionId? }) → dialectic reasoning

Phase 1: Types (src/types.ts)

  • Simplify HonchoProviderOptions — remove redundant fields that the SDK handles (env var fallbacks)
  • Add middleware config type:
    interface HonchoMiddlewareConfig {
      userId: string;
      sessionId?: string;
      assistantId?: string;        // default: "assistant"
      persistInput?: boolean;      // default: true
      injectHistory?: boolean;     // default: true
      formatContext?: (context: SessionContext) => string;
      onError?: (error: unknown) => void;
    }
  • Add tools config type:
    interface HonchoToolsConfig {
      userId: string;
      sessionId?: string;
      assistantId?: string;
    }
  • Remove old types: ResolvedHonchoConfig, ResolvedSessionConfig, PeerRoleMap, HonchoCallOptions (no longer needed)

Phase 2: Middleware (src/ai-sdk/middleware.ts)

  • Single createMiddleware(honcho, config) function:
    • transformParams:
      1. const userPeer = await honcho.peer(config.userId) (SDK handles lazy getOrCreate)
      2. If sessionId: const session = await honcho.session(config.sessionId)session.context({ peer_perspective: assistantId, peer_target: userId, summary: true }) → format and inject
      3. If no sessionId: userPeer.getContext() → inject representation + card only
      4. Branch on injectHistory to include/exclude recent messages
    • wrapGenerate:
      1. If no sessionId: no-op
      2. Build messages array: if persistInput + first step, include user message as userId’s; always include output as assistantId’s
      3. session.addMessages(messages)
    • wrapStream: same but collect chunks via TransformStream
  • Context formatting: use SessionContext.toOpenAI(assistantId) for default formatting, or custom formatContext if provided. Extract the system-prompt-relevant parts (representation, card, summary) and inject into system prompt.
  • Keep tool continuation detection for persistInput: true

Phase 3: Tools (src/ai-sdk/tools.ts)

  • Rewrite tools to use SDK’s object-oriented API:
    • honcho_chat: peer.chat(query, { target }) instead of client.workspaces.peers.chat(wsId, peerId, { query })
    • honcho_search: peer.search(query) instead of client.workspaces.peers.search(wsId, peerId, { query })
    • honcho_context: session.context(...) instead of client.workspaces.sessions.context(wsId, sessionId, ...)
    • honcho_get_representation: peer.representation() instead of client.workspaces.peers.representation(wsId, peerId, {})
    • honcho_save_conclusion: peer.conclusionsOf(target).create(...) or similar
    • honcho_search_conclusions: honcho.conclusions.query(...) or similar
  • Tool factory takes Honcho instance instead of { client, workspaceId, defaultPeerId, ... }

Phase 4: Provider factory (src/ai-sdk/index.ts)

  • createHoncho(options?) → creates Honcho instance (SDK handles env vars)
  • Internal caching: Map<string, { peer, session }> keyed by composite key
  • middleware(config) → creates middleware using cached peers/sessions
  • tools(config) → creates tools bound to userId
  • send({ userId, sessionId, content })session.addMessages([peer.message(content)])
  • client → expose underlying Honcho instance

Phase 5: Clean up

  • Remove src/ai-sdk/session.ts (flat middleware replaces session object)
  • Remove src/multi-agent/index.ts (flat middleware with assistantId replaces multi-agent)
  • Remove src/shared/converters.ts (SDK’s toOpenAI()/toAnthropic() replaces custom converters)
  • Simplify src/shared/context.ts to just createClient() (most helpers now handled by SDK methods directly in middleware)
  • Update src/index.ts exports
  • Update src/shared/descriptions.ts: add assistantPeerId: 'assistant' to DEFAULTS
  • Update tsup.config.ts and package.json exports: remove multi-agent entry point

Phase 6: README rewrite

  • Quick Start: 4 lines (createHoncho + generateText with middleware)
  • “Add persistence”: add sessionId
  • “Add tools”: add honcho.tools() — highlight honcho_chat as the killer feature
  • “Multi-peer”: change assistantId
  • “Using with messages array”: set injectHistory: false
  • “Direct SDK access”: honcho.client for advanced usage, mention toOpenAI()/toAnthropic()
  • Move dreaming, identity to separate docs or “Advanced” section
  • Remove all wrapLanguageModel references

Phase 7: Tests

  • Rewrite test suite for new API
  • Context-only (no session): verify representation/card injected, no persistence
  • With session: verify output persisted as assistantId’s message
  • persistInput true/false
  • injectHistory true/false
  • Multi-peer with different assistantIds
  • Tools: standalone and with middleware
  • honcho.send()
  • messages array (no duplication)

Files to Modify

FileChange
package.jsonReplace @honcho-ai/core with @honcho-ai/sdk, remove multi-agent export
tsup.config.tsRemove multi-agent entry point
src/types.tsSimplify: remove old types, add HonchoMiddlewareConfig, HonchoToolsConfig
src/ai-sdk/index.tsRewrite: flat middleware/tools, caching, send()
src/ai-sdk/middleware.tsRewrite: single createMiddleware using SDK objects
src/ai-sdk/tools.tsRewrite: use SDK’s object-oriented API
src/shared/context.tsSimplify: just createClient(), remove v3Fetch and wrapper functions
src/shared/descriptions.tsAdd assistantPeerId default
src/index.tsUpdate exports
README.mdFull rewrite with progressive disclosure
src/test.tsRewrite for new API

Files to Remove

FileReason
src/ai-sdk/session.tsReplaced by flat middleware config
src/multi-agent/index.tsReplaced by assistantId parameter on middleware
src/shared/converters.tsReplaced by SDK’s native toOpenAI()/toAnthropic()

Files NOT Modified

FileReason
src/openai/OpenAI adapter — may need minor updates for new SDK but separate concern
src/dreaming/Kept as experimental, separate concern
src/identity/Kept as experimental, separate concern

Verification

  1. npm run typecheck — all types resolve with new SDK
  2. npm run build — tsup bundles cleanly
  3. Test suite with HONCHO_API_KEY + HONCHO_WORKSPACE_ID:
    • Context-only (no session)
    • Session with persistence
    • persistInput true/false
    • injectHistory true/false
    • Multi-peer with different assistantIds
    • Tools standalone and with middleware
    • honcho.send()
    • messages array (no duplication)
  4. Verify v3Fetch hack is gone — SDK natively speaks v3