npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

abstractflow-sdk

v1.2.2

Published

Abstractflow — zero-config JavaScript AI Agent SDK with tools, guardrails, multi-provider models, and multi-agent handoffs. Install, drop your key in .env, run.

Downloads

1,051

Readme

Abstractflow

JavaScript AI Agent SDK — builder API, Zod tools, multi-provider models, guardrails, multi-agent handoffs, and typed runtime events.

npm Node Tests License: ISC

Abstractflow is a from-scratch Agent SDK core: one agent loop that speaks JSON steps (INITALTHINKTOOL_REQUESTANALYSEOUTPUT), pluggable LLM providers (OpenAI / Claude / Gemini), typed tools, safety guardrails, human approval, and specialist handoffs with cycle/depth protection.


Table of Contents


Why this SDK

Most tutorials jump straight to a big framework. This repo builds the runtime pieces yourself:

  1. Agent loop — forced JSON step pipeline so every turn is inspectable.
  2. Tools — Zod-validated inputs before execute runs.
  3. Providers — same complete({ messages, stream, onToken }) contract for OpenAI, Claude, Gemini.
  4. Guardrails — allow / deny / modify / require_approval at four lifecycle phases.
  5. Handoffs — transfer ownership to a specialist agent with context + loop prevention.
  6. Events — listen to tool, handoff, guardrail, and run lifecycle signals.

Architecture

┌────────────────────────────────────────────────────────────────────┐
│                    YOUR APP (import from abstractflow-sdk)         │
│   defineTool · defineHandoff · guardrails · agent.on(...)          │
└───────────────────────────────┬────────────────────────────────────┘
                                │
                                ▼
┌────────────────────────────────────────────────────────────────────┐
│                         Agent Runtime (agent.js)                   │
│  Builder → instructions + tools + handoffs + guardrails            │
│  run(query) → JSON step loop → TOOL_REQUEST / handoff / OUTPUT     │
└───────┬───────────────────┬───────────────────┬────────────────────┘
        │                   │                   │
        ▼                   ▼                   ▼
┌───────────────┐  ┌────────────────┐  ┌────────────────────────────┐
│ Providers     │  │ Tools          │  │ Guardrails + Handoffs      │
│ openai        │  │ defineTool     │  │ beforeRun / beforeToolCall │
│ claude        │  │ Zod validate   │  │ afterToolCall / afterRun   │
│ gemini        │  │ execute()      │  │ defineHandoff + depth/cycle│
└───────────────┘  └────────────────┘  └────────────────────────────┘

Installation

npm install abstractflow-sdk

Node >= 20.12 required.

Abstractflow is an ESM-only package, so your package.json needs "type": "module":

{
  "type": "module",
  "dependencies": {
    "abstractflow-sdk": "^1.2.2"
  }
}

Without it, node index.js fails with Cannot use import statement outside a module on Node 20 (Node 22+ still runs it, but warns). npx abstractflow-sdk init sets this for you.

Put a provider API key in a .env file at your project root:

OPENAI_API_KEY=sk-...
# optional, depending on provider:
# ANTHROPIC_API_KEY=...
# GEMINI_API_KEY=...

That is the whole setup. Importing the SDK loads .env for you, so plain node index.js / npm start works — no --env-file flag needed. The nearest .env at or above the working directory is used, so running from a subfolder still finds the project's keys. Real environment variables always win over .env; set ABSTRACTFLOW_NO_DOTENV=1 to turn the auto-load off.

zod ships with the SDK and is re-exported, so one install covers everything:

import { Agent, defineTool, createOpenAIProvider, z } from 'abstractflow-sdk'

Scaffold a runnable project

To skip the boilerplate entirely (ESM package.json, start script, starter agent, .env):

npx abstractflow-sdk init my-agent
cd my-agent
npm install
# paste your key into .env
npm start

init never overwrites existing files — run it inside a project you already have and it only fills in what is missing.

Local development (this repo)

git clone <your-repo-url>
cd agent-sdk
npm install
cp .env.example .env   # add your keys
npm start              # runs examples/demo.js
npm test

Publish checklist (maintainers)

npm test
npm pack --dry-run     # review files that will ship
npm login
npm publish --access public

5-Minute Quick Start

Save this as index.js:

import {
  Agent,
  defineTool,
  createOpenAIProvider,
  z,
} from 'abstractflow-sdk'

const weatherTool = defineTool({
  name: 'fetchWeatherInfo',
  description: 'Fetches realtime weather by city name',
  inputSchema: z.object({
    cityName: z.string().min(1),
  }),
  async execute({ cityName }) {
    // call your API here
    return { cityName, weatherInfo: 'Sunny +30C' }
  },
})

const agent = Agent.builder()
  .name('assistant')
  .setInstructions('You help users. Use fetchWeatherInfo for weather questions.')
  .provider(createOpenAIProvider())
  .tool(weatherTool)
  .build()

const messages = await agent.run('What is the weather in Goa?')
console.log(messages[messages.length - 1])

Put your key in .env, then run it:

node index.js

Core Concepts

1. Agent Runtime & Builder

Build agents with a fluent builder. Identity (name) matters for handoff traces and loop detection.

import { exec } from 'node:child_process'
import { promisify } from 'node:util'
import {
  Agent,
  defineTool,
  createOpenAIProvider,
  rejectInvalidInput,
  z,
} from 'abstractflow-sdk'

const execAsync = promisify(exec)

const cliAccessTool = defineTool({
  name: 'execCli',
  description: 'Runs a CLI command on the machine and returns its output',
  inputSchema: z.object({ cli: z.string().min(1) }),
  async execute({ cli }) {
    const { stdout, stderr } = await execAsync(cli, { timeout: 30_000 })
    return { stdout, stderr }
  },
})

const weatherAgent = Agent.builder()
  .name('weather')
  .setInstructions('You answer weather questions.')
  .provider(createOpenAIProvider())
  .build()

const agent = Agent.builder()
  .name('coder')
  .setInstructions('You are an expert coding agent.')
  .provider(createOpenAIProvider({ model: 'gpt-4o-mini' }))
  .tool([cliAccessTool])
  .handoff([weatherAgent])          // or defineHandoff({ agent, ... })
  .maxHandoffDepth(3)               // default: 5
  .guardrail([rejectInvalidInput({ schema: z.string().min(3) })])
  .onApproval(async ({ reason, context }) => {
    console.warn('Approve?', reason, context.toolName)
    return true // or false to deny
  })
  .build()

const result = await agent.run('ls the current directory')
console.log(result[result.length - 1])

execCli runs whatever the model asks for. It is used throughout this README because it makes the guardrail examples concrete — pair it with blockDangerousToolCalls and requireApproval before pointing it at anything you care about.

| Method | Purpose | | :--- | :--- | | .name(string) | Agent identity (required for safe handoffs) | | .setInstructions(string) | Your system prompt (merged with the harness) | | .provider(p) | OpenAI / Claude / Gemini / custom | | .tool(t \| t[]) | Register defineTool(...) tools | | .handoff(h \| h[]) | Specialist agents or defineHandoff(...) | | .maxHandoffDepth(n) | Cap nested handoffs (>= 1) | | .maxLoop(n) | Cap pipeline turns per run (default 30) | | .maxParseRetries(n) | Consecutive bad-format replies tolerated (default 3) | | .guardrail(g \| g[]) | Safety checks at lifecycle phases | | .onApproval(fn) | Human/process approval callback | | .build() | Create the Agent instance |

When a model will not hold the format

A model that cannot produce the JSON step will not learn it on attempt 30, and every retry resends a longer history. After maxParseRetries consecutive unparseable replies the run stops with a StepFormatError naming the provider:

import { Agent, createOpenAIProvider, StepFormatError } from 'abstractflow-sdk'

const agent = Agent.builder()
  .name('assistant')
  .setInstructions('You are helpful.')
  .provider(createOpenAIProvider())
  .maxParseRetries(3)
  .build()

try {
  await agent.run('Explain BODMAS briefly')
} catch (err) {
  if (err instanceof StepFormatError) {
    console.error(err.message)  // Provider "claude" (claude-sonnet-4) returned 3 …
    console.error(err.raw)      // the last reply, so you can see what it sent
  } else {
    throw err
  }
}

Measured on a doomed run with one tool registered — grinding to maxLoop cost 30 calls / ~59k input tokens / ~90s; failing fast costs 3 calls / ~3.1k tokens / ~9s. The counter resets as soon as the model produces one valid step, so a model that stumbles once is not punished.


2. Typed Tools (defineTool)

Every tool has a name, description, Zod inputSchema, and execute. Invalid input never reaches your function — you get { ok: false, code: 'VALIDATION_ERROR', ... }.

import { defineTool, z } from 'abstractflow-sdk'

const calcTax = defineTool({
  name: 'calculate_tax',
  description: 'Calculates 18% tax for an amount',
  inputSchema: z.object({
    amount: z.number().positive(),
  }),
  async execute({ amount }) {
    return { tax: amount * 0.18, total: amount * 1.18 }
  },
})

What the LLM sends (one JSON step at a time):

{
  "step": "TOOL_REQUEST",
  "functionName": "calculate_tax",
  "input": { "amount": 100 }
}

For single-field schemas, a plain string is also coerced into that field.

Return shape from tool.execute:

| Result | Meaning | | :--- | :--- | | { ok: true, data } | Success — data goes back into message history | | { ok: false, code: 'VALIDATION_ERROR', error } | Zod failed | | { ok: false, code: 'EXECUTION_ERROR', error } | execute threw |


3. Model Providers

All providers implement the same contract:

{
  name: string,
  model?: string,
  complete({ messages, stream, onToken }): Promise<string>
}

Each factory reads its own environment variable and throws immediately if the key is missing — so build the one you actually have a key for, rather than constructing all three up front:

| Factory | Key it reads | Default model | | :--- | :--- | :--- | | createOpenAIProvider() | OPENAI_API_KEY | gpt-4o-mini | | createClaudeProvider() | ANTHROPIC_API_KEY | claude-sonnet-4-20250514 | | createGeminiProvider() | GEMINI_API_KEY / GOOGLE_API_KEY | gemini-2.0-flash |

import {
  Agent,
  createOpenAIProvider,
  createClaudeProvider,
  createGeminiProvider,
} from 'abstractflow-sdk'

function pickProvider() {
  if (process.env.ANTHROPIC_API_KEY) {
    return createClaudeProvider({ model: 'claude-sonnet-4-20250514' })
  }
  if (process.env.GEMINI_API_KEY) {
    return createGeminiProvider({ model: 'gemini-2.0-flash' })
  }
  return createOpenAIProvider({ model: 'gpt-4o-mini' })
}

const agent = Agent.builder()
  .name('bot')
  .setInstructions('Be helpful.')
  .provider(pickProvider()) // same agent code on any provider
  .build()

console.log(agent.provider.name, agent.provider.model)

Every factory also takes an explicit { apiKey, model } if you would rather not rely on the environment: createClaudeProvider({ apiKey: myKey }).

Local & OpenAI-compatible servers

Anything that speaks the OpenAI Chat Completions API — Ollama, LM Studio, vLLM, Groq, OpenRouter, Together — works through one factory. Guardrails, handoffs and events behave identically on these models, because they all go through the same complete(...) contract.

import { createOpenAICompatibleProvider } from 'abstractflow-sdk'

// Ollama, fully local, no API key
const ollama = createOpenAICompatibleProvider({
  baseURL: 'http://localhost:11434/v1',
  model: 'llama3.1',
  name: 'ollama', // optional label shown in run_started / traces
})

// Groq
const groq = createOpenAICompatibleProvider({
  baseURL: 'https://api.groq.com/openai/v1',
  apiKey: process.env.GROQ_API_KEY,
  model: 'llama-3.3-70b-versatile',
  jsonMode: true, // only if the server supports response_format
})

jsonMode defaults to false because many of these servers reject response_format. You rarely need it: the agent loop already recovers the step object from markdown fences or surrounding prose, so models without a JSON mode (Claude and most local models) still drive the pipeline.

You can also hand-write a custom provider — any object exposing complete({ messages, stream, onToken }) is accepted by .provider(...).


4. Guardrails

Guardrails run at four phases and return a decision:

| Phase | When | Typical use | | :--- | :--- | :--- | | beforeRun | Before the loop starts | Validate / block user input | | beforeToolCall | Before a tool executes | Block dangerous commands, require approval | | afterToolCall | After a tool returns | Redact secrets from tool output | | afterRun | After the run finishes | Validate final OUTPUT step, redact history |

Decision actions: allow | deny | modify | require_approval

Built-in helpers

import { exec } from 'node:child_process'
import { promisify } from 'node:util'
import {
  Agent,
  defineTool,
  createOpenAIProvider,
  rejectInvalidInput,
  rejectPromptLeakRequests,
  blockDangerousToolCalls,
  requireApproval,
  redactSensitiveInfo,
  validateStructuredOutput,
  z,
} from 'abstractflow-sdk'

const execAsync = promisify(exec)

const cliAccessTool = defineTool({
  name: 'execCli',
  description: 'Runs a CLI command on the machine and returns its output',
  inputSchema: z.object({ cli: z.string().min(1) }),
  async execute({ cli }) {
    const { stdout, stderr } = await execAsync(cli, { timeout: 30_000 })
    return { stdout, stderr }
  },
})

const agent = Agent.builder()
  .name('coder')
  .setInstructions('You are a careful coding agent.')
  .provider(createOpenAIProvider())
  .tool(cliAccessTool)
  .guardrail([
    // beforeRun — reject empty / too-short queries
    rejectInvalidInput({ schema: z.string().min(3) }),

    // beforeRun — block "show me your system prompt" / jailbreak attempts
    rejectPromptLeakRequests(),

    // beforeToolCall — deny destructive CLI patterns
    blockDangerousToolCalls({
      toolName: 'execCli',
      patterns: [/rm\s+-rf/i, /sudo\b/i, /mkfs/i],
    }),

    // beforeToolCall — pause for human approval
    requireApproval({
      toolNames: ['execCli'],
      reason: 'CLI execution requires approval',
    }),

    // afterToolCall — scrub secrets from tool results
    redactSensitiveInfo({
      patterns: [
        { match: /sk-[A-Za-z0-9_-]+/g, replace: '[REDACTED_API_KEY]' },
      ],
    }),

    // afterRun — also scrub final message history
    redactSensitiveInfo({
      name: 'redactSensitiveMessages',
      phase: 'afterRun',
      patterns: [
        { match: /sk-[A-Za-z0-9_-]+/g, replace: '[REDACTED_API_KEY]' },
      ],
    }),

    // afterRun — final assistant step must be OUTPUT
    validateStructuredOutput({ requiredStep: 'OUTPUT' }),
  ])
  .onApproval(async ({ reason, context }) => {
    console.warn(`[approval] ${reason}`, context.toolName)
    return true
  })
  .build()

const messages = await agent.run('list the files in this folder')
console.log(messages[messages.length - 1])

Custom guardrail

import { defineGuardrail } from 'abstractflow-sdk'

const blockWeekends = defineGuardrail({
  name: 'blockWeekends',
  phase: 'beforeRun',
  async run({ query }) {
    const day = new Date().getDay()
    if (day === 0 || day === 6) {
      return { action: 'deny', reason: 'Agent disabled on weekends' }
    }
    return { action: 'allow' }
  },
})

On deny, the run throws GuardrailError (with phase + guardrail name) and emits run_failed / guardrail_triggered.


5. Human-in-the-Loop Approval

Sensitive tools can require an explicit approve callback via requireApproval + .onApproval(...).

import { exec } from 'node:child_process'
import { promisify } from 'node:util'
import {
  Agent,
  defineTool,
  createOpenAIProvider,
  requireApproval,
  z,
} from 'abstractflow-sdk'

const execAsync = promisify(exec)

const cliAccessTool = defineTool({
  name: 'execCli',
  description: 'Runs a CLI command on the machine and returns its output',
  inputSchema: z.object({ cli: z.string().min(1) }),
  async execute({ cli }) {
    const { stdout, stderr } = await execAsync(cli, { timeout: 30_000 })
    return { stdout, stderr }
  },
})

const agent = Agent.builder()
  .name('admin')
  .setInstructions('Use execCli carefully.')
  .provider(createOpenAIProvider())
  .tool(cliAccessTool)
  .guardrail([
    requireApproval({
      toolNames: ['execCli'],
      reason: 'CLI execution requires approval',
    }),
  ])
  .onApproval(async ({ reason, context }) => {
    // Wire this to a UI, Slack message, CLI prompt, etc.
    console.warn(`[approval] ${reason}`)
    console.warn(`tool=${context.toolName} input=`, context.input)
    return false // deny → tool does not run
  })
  .build()

const messages = await agent.run('list the files in this folder')
console.log(messages[messages.length - 1])

If approval returns false (or no handler is set), the tool call is denied fail-closed.


6. Multi-Agent Handoffs

Transfer conversation ownership to a specialist. Handoffs are exposed to the LLM as normal tools (handoffToWeather, etc.).

import { Agent, defineHandoff, defineTool, createOpenAIProvider, z } from 'abstractflow-sdk'

const weatherTool = defineTool({
  name: 'fetchWeatherInfo',
  description: 'Fetches weather by city',
  inputSchema: z.object({ cityName: z.string().min(1) }),
  async execute({ cityName }) {
    return { cityName, weatherInfo: 'Cloudy 22C' }
  },
})

const weatherAgent = Agent.builder()
  .name('weather')
  .setInstructions('You answer weather questions using fetchWeatherInfo.')
  .provider(createOpenAIProvider())
  .tool(weatherTool)
  .build()

const coder = Agent.builder()
  .name('coder')
  .setInstructions(
    'You are a coding agent. For weather questions, hand off to the weather agent.',
  )
  .provider(createOpenAIProvider())
  // .tool(yourTool)   ← register this agent's own tools here
  .handoff([
    defineHandoff({
      agent: weatherAgent,
      description: 'Transfer weather questions to the weather specialist',
      // optional: trim history before the specialist sees it
      filterContext: (messages) => messages.filter((m) => m.role !== 'developer'),
      // optional: hook before target runs
      onHandoff: async ({ from, to, input }) => {
        console.log(`handing off ${from} → ${to}`, input)
      },
    }),
  ])
  .maxHandoffDepth(3)
  .build()

const result = await coder.run('what is the weather in Saharanpur?')
console.log(result[result.length - 1])

What happens under the hood:

  1. LLM emits TOOL_REQUEST for handoffToWeather with { task, context? }.
  2. SDK checks cycle (target already in chain) and max depth.
  3. Packs prior messages + a developer HANDOFF notice.
  4. Emits handoff_started → runs target agent → emits handoff_completed.
  5. Target’s final messages become the source run’s result.

Safety:

| Rule | Behavior | | :--- | :--- | | Cycle | coder → weather → coder throws HandoffError | | Max depth | Nested transfers beyond .maxHandoffDepth(n) throw HandoffError |

Shortcut: .handoff([weatherAgent]) auto-wraps with defineHandoff({ agent }).


7. Runtime Events & Tracing

Agent extends Node’s EventEmitter. Use AgentEvent constants for stable names.

import { Agent, AgentEvent, createOpenAIProvider } from 'abstractflow-sdk'

const agent = Agent.builder()
  .name('assistant')
  .setInstructions('You are helpful.')
  .provider(createOpenAIProvider())
  .build()

agent.on(AgentEvent.RUN_STARTED, (data) => {
  console.log('run_started', data.provider, data.model, data.agent)
})

agent.on(AgentEvent.TOOL_STARTED, (data) => {
  console.log('[tool_started]', data.toolName, data.input)
})

agent.on(AgentEvent.TOOL_COMPLETED, (data) => {
  console.log('[tool_completed]', data.toolName, data.ok)
})

agent.on(AgentEvent.HANDOFF_STARTED, (data) => {
  console.log(`[handoff] ${data.from} → ${data.to}`, data.input)
})

agent.on(AgentEvent.HANDOFF_COMPLETED, (data) => {
  console.log(`[handoff done] ${data.from} → ${data.to} ok=${data.ok}`)
})

agent.on(AgentEvent.GUARDRAIL_TRIGGERED, (data) => {
  console.log('[guardrail]', data.action, data.reason)
})

agent.on(AgentEvent.TEXT_STREAMED, ({ delta }) => {
  process.stdout.write(delta)
})

// Every turn of the step pipeline, live — INITAL / THINK / ANALYSE included
agent.on(AgentEvent.STEP, (data) => {
  if (!data.ok) return console.warn('[bad step]', data.error)
  console.log(`[${data.index}] ${data.step}: ${data.text ?? data.functionName}`)
})

agent.on(AgentEvent.RUN_COMPLETED, () => console.log('[run_completed]'))
agent.on(AgentEvent.RUN_FAILED, (data) => console.error('[run_failed]', data.error))

await agent.run('What is the weather in Goa?')

| Event | Fired when | | :--- | :--- | | run_started | run() begins | | text_streamed | Streaming token delta | | step | One pipeline turn parsed (ok:false if the model broke format) | | tool_started | Tool / handoff tool about to run | | tool_completed | Tool finished (ok true/false) | | handoff_started | Transfer to specialist begins | | handoff_completed | Transfer finished (ok) | | guardrail_triggered | Guardrail deny / approval path | | run_completed | Loop finished successfully (carries the parsed output) | | run_failed | Error / guardrail / handoff failure |

step carries { index, agent, ok, step, text, functionName, input, raw }. It is the whole reasoning trace as it happens — no hidden reasoning tokens — which is what makes a run auditable while it runs, not only from the history run() returns at the end.

Reading the final answer

run() returns the message history with every model reply untouched, which is what you want for auditing but awkward to read from. The parsed final step is available separately:

import { Agent, AgentEvent, createOpenAIProvider } from 'abstractflow-sdk'

const agent = Agent.builder()
  .name('assistant')
  .setInstructions('You are helpful.')
  .provider(createOpenAIProvider())
  .build()

// or read it from the event
agent.on(AgentEvent.RUN_COMPLETED, ({ output }) => console.log(output.text))

await agent.run('What is the weather in Goa?')

console.log(agent.lastOutput.text) // "Goa is sunny, 30C"

lastOutput is reset at the start of every run and is undefined if the run failed. After a handoff it holds the specialist's output.


8. Streaming

Pass { stream: true } to stream model tokens and listen on text_streamed.

import { Agent, AgentEvent, createOpenAIProvider } from 'abstractflow-sdk'

const agent = Agent.builder()
  .name('assistant')
  .setInstructions('You are helpful.')
  .provider(createOpenAIProvider())
  .build()

agent.on(AgentEvent.TEXT_STREAMED, ({ delta }) => process.stdout.write(delta))

const messages = await agent.run('Explain BODMAS briefly', { stream: true })
console.log('\n', messages[messages.length - 1])

Token-by-token streaming is real on the OpenAI and OpenAI-compatible providers. The Claude and Gemini providers keep the same API but emit the response as a single text_streamed event once it arrives.


9. Message Interceptors

Observe every message pushed into history (user, assistant, developer, tool feedback):

import { Agent, createOpenAIProvider } from 'abstractflow-sdk'

const agent = Agent.builder()
  .name('assistant')
  .setInstructions('You are helpful.')
  .provider(createOpenAIProvider())
  .build()

agent.attachInterceptor((message) => {
  console.log(`${message.role}: ${message.content}`)
})

await agent.run('What is the weather in Goa?')

Useful for debugging the step pipeline without wiring every event.


Step Pipeline (how the agent thinks)

The harness forces the model to emit one JSON object per turn:

{
  "step": "INITAL" | "THINK" | "TOOL_REQUEST" | "ANALYSE" | "OUTPUT",
  "text": "...",
  "functionName": "optional tool name",
  "input": {}
}

| Step | Meaning | | :--- | :--- | | INITAL | Understand the user intent | | THINK | Plan / break down the problem | | TOOL_REQUEST | Call a tool or handoff | | ANALYSE | Verify intermediate results | | OUTPUT | Final answer (ends the loop) |

Example weather flow:

  1. INITAL — user wants weather for Goa
  2. THINK — use fetchWeatherInfo
  3. TOOL_REQUEST{ functionName: "fetchWeatherInfo", input: { cityName: "goa" } }
  4. Tool result injected as developer feedback
  5. OUTPUT — final natural-language answer

Project Layout

src/
  index.js                 # Public package entry (export from 'abstractflow-sdk')
  app/
    agent.js               # Agent + AgentBuilder + re-exports
    tool.js                # defineTool
    guardrails.js          # phases + built-in guardrails
    handoff.js             # defineHandoff, cycle/depth checks, context packing
    events.js              # AgentEvent constants
    config.js              # HARNESS_PROMPT (step pipeline)
    env.js                 # .env auto-loader
    providers/
      base.js              # provider contract helpers
      openai.js
      claude.js
      gemini.js
      index.js
bin/
  abstractflow.js          # `npx abstractflow-sdk init` scaffolder
examples/
  demo.js                  # Local demo (npm start)

Run & Test

# run the demo agent (reads .env)
npm start

# unit tests (tools, guardrails, handoffs, providers, events)
npm test

# see what npm will publish
npm run pack:check

License

ISC — see LICENSE.