Skip to main content

twenty-sdk vs @agentkit — comparison & tool/function reconciliation

Companion to the extraction inventory. Settles how @agentkit relates to twenty-sdk so we build a bridge, not a duplicate.

They solve different problems

twenty-sdk@agentkit
PurposeBuild & deploy CRM extension apps into TwentyA reusable AI agent runtime
Unit of workAn "app": metadata + logic-functions + UI componentsAn agent: model + tools + durable runs + sessions
Shipped asA twenty CLI (scaffold/dev/plan/apply), manifest-drivenComposable packages (createAgentPlatform / createAgent)
ExecutionDeterministic serverless logic-functions synced to TwentyDurable queued runs, checkpoints, HITL, cancellation
UIfront-component-renderer (Preact/React)headless react + optional ui
TransportGraphQL over graphql-sse + twenty-client-sdkGraphQL subscriptions (SSE adapter, SPEC #37)
AI / models / RAG / approvals❌ none✅ the whole point

Conclusion: they coexist. An app is built with twenty-sdk and consumes @agentkit for its AI. @agentkit replaces Agno (the current runtime, see inventory), not twenty-sdk.

Evidence from the running app

twenty-apps/twenty-social uses twenty-sdk logic-functions/ for deterministic platform ops: connect-test-account, on-linkedin-connected, on-meta-connected, check-media-storage, finish-pending-targets, show-connection-setup. These are CRM/connector operations triggered by events/UI — not AI agent tools.

The decision: tools wrap logic-functions

  • @agentkit tools wrap existing twenty-sdk logic-functions / platform services — they do not reimplement connectors, publishing, media handling or storage. This matches the ShareFlow integration spec (social-integration: docs/forge-integration-spec.md): "existing publishing, connector and database services are reused behind tools."
  • A tool is a thin, agent-facing envelope over a deterministic function: it adds the permission filter (docs/11), the approval gate for external writes (docs/04), and the idempotency key — then delegates the actual side effect to the logic-function/service, which stays the source of truth.
  • No duplication rule: if a capability already exists as a logic-function, the tool calls it; only genuinely new AI-facing capabilities (e.g. generate_content, search_web) become net-new tools.
  • Effect boundary: logic-functions own the external write; the tool layer owns agent-facing authorization/approval/idempotency and result-envelope shaping.
Agent run ──▶ @agentkit tool (authz + approval + idempotency)
└──▶ twenty-sdk logic-function / platform service (the actual side effect)

SSE precedent (for SPEC #37)

twenty-sdk already ships GraphQL streaming over graphql-sse (a dependency of the SDK). That is direct precedent that Server-Sent Events is a proven transport in this stack — it de-risks the SSE transport adapter (SPEC #37) as the lightweight streaming path for the embedded profile, alongside GraphQL subscriptions for the server profile. Match the graphql-sse framing rather than inventing a bespoke SSE protocol.

Outcome

  • Comparison recorded (this doc).
  • Tool↔logic-function bridge decision recorded (above) — carried into REQ-006 (tools) and the ShareFlow integration.
  • SSE precedent recorded for SPEC #37.

Status of the SSE decision (#111)

Implemented. graphql/sse.ts now emits the graphql-sse wire format — event: next carrying an ExecutionResult, terminated by event: complete, with the id: line still carrying RunEvent.sequence so Last-Event-ID resume is unaffected.

It previously emitted event: <RunEvent.type> with a raw RunEvent as data, which is the bespoke protocol this document had already decided against; graphql-sse's own validateStreamEvent rejects that event name outright, so an existing Twenty client could not have consumed it.

A failed run is delivered as a next frame carrying both data and errors, rather than as a protocol error frame. An error frame carries no id:, so a failed run would otherwise be unresumable and a reconnecting client would never learn the run had ended.

graphql-sse is a devDependency of @forge/agentkit, used only to validate the frames in tests. The adapter still writes plain text and takes no server dependency.

The SPEC numbers above were stale: on develop, #27 is the usage hook and #37 is the SSE adapter.