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 ComponentA 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@betainput.in Receive research promptagent.tools Connect model and tools · search → collect → writeagent.out Ready to composeCOMPONENTS, PORTS, AND PIPELINES
Connect them through ports. The graph runs on the HLang flow runtime.
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 ComponentComponents 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 → outputIndependent execution with run() or reactive pipeline mode with _transform(). The same component works in both.
run(data) / _transform($i, $o)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')Core, components, integrations, and tools as separate packages. Choose the pieces your application needs.
@astack-tech/{core,components,…}Agents accept any object with chatCompletion(). The DeepSeek provider ships today; add others by implementing the same contract.
model.chatCompletion(messages)THE COMPUTATION MODEL
Components, ports and pipelines. Workflows, branching, agents and streaming are built from these three ideas, not from separate subsystems.
Extend Component and override run(). The same class works on its own and inside a pipeline, where the runtime feeds its input port.
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
Every path data can take is a connect() call you can read.
Each route's topology is built on its first run and reused afterwards.
Each connection is an RxJS stream. Components react as values arrive.
QUICK START
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 examplesnpm install @astack-tech/core@beta @astack-tech/components@beta @astack-tech/integrations@beta @astack-tech/tools@betaimport { 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 } ]
Set DEEPSEEK_API_KEY in your shell, then run your TypeScript file. Keep API keys on the server.
npx tsx index.tsHOW ASTACK COMPARES
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.
| Aspect | AStack | PocketFlow | Haystack |
|---|---|---|---|
| Language | TypeScript | Python | Python |
| Unit of work | Component with named ports | Node with prep, exec and post | Component with typed sockets |
| How data moves | Along each connection, from an output port to an input port | Through a shared store that nodes read and write | Along connections between sockets |
| Branching | A component sends to the output port it chooses | post returns an action; the flow follows that labeled edge | Router components |
| Execution | Reactive streams; concurrent runs of a route resolve in order | A flow runs nodes one after another; async and parallel variants exist | A pipeline run loop |
| Design goal | A typed, reactive data-flow model | A minimal core of about 100 lines | A 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 comparisonEXAMPLES
Each example is one short file that shows one idea. The code you read here is the code that runs in CI.
Inside AStack's recursive language model implementation.