Open source · TypeScript · MIT

Public betaAStack is under active development. Build with us.Release notes

Build AI agents
with components.

A TypeScript framework for AI applications. Components exchange data through ports, and a pipeline connects them into a graph that runs on HLang's reactive flow runtime.

Start building
npm install @astack-tech/core@beta
Read the docs
@astack-tech/core 0.2.0-beta.5 running in your browser
  1. trimTrim
  2. upperUppercase
  3. exclaimExclaim
Output

Everything is a component.

Connect them through ports. The graph runs on the HLang flow runtime.

class Agent extends Component

One component model

Component extends HLang’s TransformNode. Models, agents and your own logic share one interface: input ports, output ports and run(). Pipelines connect them.

input → transform → output

Data flow paradigm

Components never call each other. You declare the connections, and the runtime sends each value along them. Model calls stay stateless; the agent keeps the history.

Dual execution modes

Independent execution with run() or reactive pipeline mode with _transform(). The same component works in both.

Named ports

Port.I() and Port.O() declare named ports. A component chooses which output port to send on, which is how branching works.

Pluggable models

Agents accept any object with chatCompletion(). DeepSeek and any OpenAI-compatible API work today, including OpenAI, Qwen and local models through Ollama; add others by implementing the same contract.

@astack-tech/{core,components,…}

Modular design

Core, components, integrations, and tools as separate packages. Choose the pieces your application needs.

Read about components

One model. Four ways to build.

Components, ports and pipelines. Workflows, branching, agents and streaming are built from these three ideas, not from separate subsystems.

A component is a unit of work

Extend Component and override run(). The same class works on its own and inside a pipeline, where the runtime feeds its input port.

Input portComponentOutput port

  • Standalone: run(data)
  • Reactive: _transform($i, $o)
  • Extra named ports
examples/component/index.ts
import { Component } from '@astack-tech/core';

/**
 * A component is a class with input and output ports.
 * `run()` is its standalone mode: call it like a function.
 */
class Uppercase extends Component {
  constructor() {
    super({});
  }

  override run(text: unknown): string {
    return String(text).toUpperCase();
  }
}

const uppercase = new Uppercase();

console.log(uppercase.run('hello, astack'));
// HELLO, ASTACK

Explicit connections

Every path data can take is a connect() call you can read.

Built once

Each route's topology is built on its first run and reused afterwards.

Reactive streams

Each connection is an RxJS stream. Components react as values arrive.

From install to first agent.

Install the packages. Create an agent with a model provider. Run the agent from your terminal.

Node.js 20+ · npm, pnpm, or Yarn

  1. Install AStack

    npm install @astack-tech/core@beta @astack-tech/components@beta @astack-tech/integrations@beta @astack-tech/tools@beta
  2. Run your agent

    Set your model provider's API key in your shell (the example uses DEEPSEEK_API_KEY), then run your TypeScript file. Keep API keys on the server.

    npx tsx index.ts
  3. Create your first agent

    examples/agent/index.ts
    import { Agent } from '@astack-tech/components';
    import { ModelProvider } from '@astack-tech/integrations';
    import { createTool } from '@astack-tech/tools';
    
    const apiKey = process.env['DEEPSEEK_API_KEY'];
    if (apiKey === undefined || apiKey === '') {
      throw new Error('Set DEEPSEEK_API_KEY to run this example.');
    }
    
    const multiply = createTool(
      'multiply',
      'Multiply two numbers.',
      args => Number(args['a']) * Number(args['b']),
      {
        type: 'object',
        properties: {
          a: { type: 'number', description: 'First factor' },
          b: { type: 'number', description: 'Second factor' },
        },
        required: ['a', 'b'],
      }
    );
    
    // The agent asks the model, runs the tools it requests, and repeats until the model answers.
    const agent = new Agent({
      // Any model works here: OpenAICompatible for OpenAI, Qwen or Ollama, or your own ModelProvider.
      model: new ModelProvider.Deepseek({ apiKey }),
      tools: [multiply],
      systemPrompt: 'Use tools for arithmetic.',
      maxIterations: 5,
    });
    
    const output = await agent.run('What is 37 times 43?');
    
    console.log(output.message);
    console.log(output.toolCalls);
    // [ { tool: 'multiply', args: { a: 37, b: 43 }, result: 1591 } ]
    
    Read this example step by step

The graph is the framework.

AStack's computation model is original. PocketFlow reached a similar idea on its own: a small core where agents, workflows and RAG are patterns, not features. PocketFlow makes a 100-line core its goal; AStack does not optimize for line count. Its API shape takes inspiration from Haystack.

Computation models compared
AspectAStackPocketFlowHaystack
LanguageTypeScriptPythonPython
Unit of workComponent with named portsNode with prep, exec and postComponent with typed sockets
How data movesAlong each connection, from an output port to an input portThrough a shared store that nodes read and writeAlong connections between sockets
BranchingA component sends to the output port it choosespost returns an action; the flow follows that labeled edgeRouter components
ExecutionReactive streams; concurrent runs of a route resolve in orderA flow runs nodes one after another; async and parallel variants existA pipeline run loop
Design goalA typed, reactive data-flow modelA minimal core of about 100 linesA full framework for search and RAG

The main difference from PocketFlow is where state lives. PocketFlow nodes share one store. In AStack each connection carries the data for that step, so every input and output is explicit in the graph.

Read the full comparison

Start with working code.

Each example is one short file that shows one idea. The code you read here is the code that runs in CI.