Menu

Core Architecture

Relevant source files

Purpose and Scope

This document provides a comprehensive examination of AStack's core architectural layers, from its foundation on HLang's Flow-Based Programming runtime to its component abstractions and execution model. It covers the structural design principles, key interfaces, and how different architectural layers interact to enable the "everything is a component" philosophy.

For implementation details of specific components like Agent and StreamingAgent, see Agent System. For information about package organization and dependencies, see Package System. For practical usage patterns and execution modes, see Computation Patterns.

Architectural Layers Overview

AStack's architecture is organized into distinct, well-defined layers that build upon each other to provide a flexible yet powerful framework for AI applications.

Layer Hierarchy Diagram

Sources: README.md91-122 README.zh-CN.md91-122

Foundation Layer

HLang Runtime Integration

AStack is built on top of @hlang-org/runtime, which provides the core Flow-Based Programming (FBP) infrastructure. This foundation enables AStack's reactive data flow model and component-based architecture.

HLang FeatureAStack Usage
Node interfaceBase type for all components
Flow classPipeline execution engine wrapper
Port-based communicationZero-adaptation component connections
Event-driven processingReactive data flow patterns

The Pipeline class directly imports and uses HLang's runtime:

Sources: packages/core/src/pipeline/index.ts1-2 packages/core/src/pipeline/index.ts10 README.md24-26

Monadic Design Pattern

AStack incorporates monadic functional programming principles to achieve composability and state management:

PrincipleImplementation
Encapsulated StateEach component maintains isolated internal state
Chainable OperationsComponents connect via ports to create fluent pipelines
Composable TransformationsComplex workflows built from simple, reusable units
Error PropagationErrors flow through pipeline in controlled manner

This design pattern influences how components are structured and how they interact, enabling both functional purity and practical stateful operations when needed.

Sources: README.md439-448 README.zh-CN.md439-448

Core Abstractions Layer

Component Class

The Component class is the fundamental building block of AStack. Every component, from simple transformers to complex agents, extends this base class.

Component Class Structure

Sources: packages/core/src/component/index.ts1-40

Component Implementation Details

The Component class extends TransformNode from @hlang-org/runtime and provides default port configuration:

MemberTypePurpose
Component.Porttypeof PortStatic reference to Port factory from HLang
inPortPort.IDefault input port named 'in'
outPortPort.ODefault output port named 'out'

The constructor automatically creates and attaches default ports:

Sources: packages/core/src/component/index.ts3-17

Component Execution Methods

MethodModePurpose
run(data)StandaloneDirect component invocation, returns processed data
_transform($i, $o)PipelinePort-based processing for pipeline execution

The base run() method is a pass-through that subclasses override:

The _transform() method implements reactive port-based processing:

This design connects the standalone run() method with pipeline execution by calling run() internally when data arrives on the input port.

Sources: packages/core/src/component/index.ts19-34 examples/text-splitter/index.ts52-76

Pipeline Class

The Pipeline class provides the orchestration engine for connecting and executing components. It manages component lifecycle, port connections, and data flow.

Pipeline Internal Architecture

Pipeline Key Methods

MethodVisibilityPurposeSignature
addComponentPublicRegister component in pipeline(name: string, instance: Node) => void
getComponentPublicRetrieve registered component(name: string) => Node | undefined
connectPublicLink output port to input port(source: string, sink: string) => void
runPublicExecute pipeline with parameters<T>(trigger: string, params: unknown) => Promise<T>
fromPrivateResolve source port reference(name: string) => Port.O
toPrivateResolve destination port reference(name: string) => Port.I
runWithBaseModePrivateInternal execution orchestration<T>(trigger: string, params: unknown, sink: (T) => void) => void

The Pipeline uses dot notation for port references: componentName.portName. For example:

  • textSplitter.text refers to the text input port of the textSplitter component
  • textSplitter.out refers to the out output port of the textSplitter component

Sources: packages/core/src/pipeline/index.ts8-188 packages/core/src/pipeline/index.ts23-58 packages/core/src/pipeline/index.ts66-76 examples/text-splitter/index.ts59-76

Pipeline Execution Flow

The Pipeline automatically creates hidden internal components identified by START_COMPONENT_NAME and END_COMPONENT_NAME constants to manage input injection and output collection. These components are:

  • BaseProducer: Injects initial parameters into the pipeline via its produce() method
  • BaseConsumer: Collects final results from the pipeline via its consume() callback

The runningTrigger set tracks which trigger points have been initialized to avoid redundant connection setup on subsequent executions.

Sources: packages/core/src/pipeline/index.ts6 packages/core/src/pipeline/index.ts99-171 packages/core/src/pipeline/index.ts14 packages/core/src/pipeline/index.ts179-185

Port System and Zero-Adaptation

The Port System is AStack's mechanism for zero-adaptation component communication. Components connect directly through ports without intermediate adapters or transformation layers.

Port Connection Model

Port Resolution Process

The Pipeline's from() and to() private methods handle port resolution using dot notation:

Output Port Resolution (from method):

Input: "componentName.portName" Steps: 1. Split string on '.' → [componentName, portName] 2. Lookup component in components Map 3. Call component.O(portName) 4. Return output Port object 

Input Port Resolution (to method):

Input: "componentName.portName" Steps: 1. Split string on '.' → [componentName, portName] 2. Lookup component in components Map 3. Call component.I(portName) 4. Return input Port object 

Both methods throw errors if the component is not found in the registry.

Sources: packages/core/src/pipeline/index.ts23-58

Connection Establishment

The connect() method establishes direct port-to-port connections:

pipeline.connect('textSplitter.out', 'handler.in') ↓ 1. this.from('textSplitter.out') → Output Port object 2. this.to('handler.in') → Input Port object 3. outputPort.connect(inputPort) → Direct connection 4. connectPairs.push([source, sink]) → Track for graph analysis 

The connectPairs array stores all connections for determining pipeline endpoints during execution setup.

Sources: packages/core/src/pipeline/index.ts84-90 packages/core/src/pipeline/index.ts11

Zero-Adaptation Benefits

Traditional ApproachAStack Zero-Adaptation
Requires adapter classesDirect port connections
Data transformation overheadMinimal data movement
Complex connection setupSimple dot notation
Multiple serialization stepsType-safe data flow

Sources: README.md45-47 README.zh-CN.md45-47

Built-in Components Layer

AStack provides a set of built-in components that extend the base Component interface for common AI application patterns.

Component Type Hierarchy

Each built-in component:

  • Implements the Component interface (_transform and run methods)
  • Defines specific input and output ports
  • Provides specialized functionality for AI workflows
  • Maintains compatibility with the Pipeline execution model

For detailed documentation of each component type, see:

Sources: README.md28-35 examples/text-splitter/index.ts2

Tool System Architecture

The Tool System provides an extensible interface for integrating external capabilities into agents and components.

Tool Interface Structure

Tools integrate with agents through a standardized interface that the LLM can understand and invoke. The agent manages the tool execution loop, passing results back to the LLM for reasoning.

For detailed tool integration patterns, see Tool Integration.

Sources: README.md236-243 README.zh-CN.md236-243

Model Provider Architecture

The Model Provider system abstracts LLM integrations, enabling AStack to work with multiple AI model providers through a consistent interface.

Model Provider Interface

The abstraction enables:

  • Switching between model providers without code changes
  • Consistent message format across providers
  • Unified streaming and non-streaming interfaces
  • Provider-specific configuration isolation

For detailed model provider implementation, see Integrations Package and Model Provider Interface.

Sources: README.md244-259 README.zh-CN.md244-259

Execution Modes

AStack components support two distinct execution modes, providing flexibility for different use cases.

Execution Mode Comparison

Execution Mode Example

The text-splitter example demonstrates both modes:

ModeUse CaseInvocationReturn Type
StandaloneSingle component, quick scriptscomponent.run(input)Component-defined (often synchronous)
PipelineComplex workflows, multiple componentspipeline.run(trigger, input)Promise<T> (always asynchronous)

Key Differences:

  • Standalone mode calls the component's run() method directly
  • Pipeline mode uses the component's _transform() method for reactive port-based execution
  • Pipeline mode enables component composition and complex data flows
  • Standalone mode provides a simpler synchronous-style API for single-component use cases

Sources: examples/text-splitter/index.ts48-76 packages/core/src/component/index.ts19-34 README.md312-326

Architecture Summary

AStack's core architecture achieves its design goals through:

Key Architectural Principles

PrincipleImplementationBenefit
Everything is a ComponentUnified Component interfaceConsistency, composability
Zero-AdaptationDirect port connectionsPerformance, simplicity
Layered DesignClear separation of concernsMaintainability, extensibility
Dual Execution ModesStandalone and Pipeline supportFlexibility, ease of use
HLang FoundationFlow-Based Programming runtimeReactive patterns, event-driven

Component Lifecycle

The architecture enables developers to:

  1. Create components by implementing the Component interface
  2. Use components standalone via the run() method
  3. Compose components into pipelines via addComponent() and connect()
  4. Execute pipelines with the run() method, which manages the full lifecycle

This layered approach provides both low-level control and high-level abstractions, allowing developers to choose the appropriate level of complexity for their use case.

Sources: README.md37-51 packages/core/src/pipeline/index.ts8-188 examples/text-splitter/index.ts1-80