Public beta AStack is under active development. Build with us.Release notes
OPEN SOURCE · TYPESCRIPT · MIT

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.

npm install @astack-tech/core@beta
research.pipeline01 — COMPONENT GRAPH
Components connected through ports.
Execution preview Local illustration · no API calls
  1. input.in Receive research prompt
  2. agent.tools Connect model and tools · search → collect → write
  3. agent.out Ready to compose
Framework foundationsTypeScript nativeObservable streamsFlow-based programmingMIT licensed

COMPONENTS, PORTS, AND PIPELINES

Everything is a component.

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

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.

class Agent extends Component
Input portComponentOutput port

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.

input → transform → output

Dual execution modes

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

run(data) / _transform($i, $o)

Named ports

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

Port.I('in') → Port.O('out')

Modular design

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

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

Pluggable models

Agents accept any object with chatCompletion(). The DeepSeek provider ships today; add others by implementing the same contract.

model.chatCompletion(messages)
Read about components

THE COMPUTATION MODEL

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.

QUICK START

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

Read the guide Browse examples

1Install AStack

npm install @astack-tech/core@beta @astack-tech/components@beta @astack-tech/integrations@beta @astack-tech/tools@beta

2Create 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) {
  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({
  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 } ]

3Run your agent

Set DEEPSEEK_API_KEY in your shell, then run your TypeScript file. Keep API keys on the server.

npx tsx index.ts
Read this example step by step

HOW ASTACK COMPARES

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