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.
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.
Sources: README.md91-122 README.zh-CN.md91-122
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 Feature | AStack Usage |
|---|---|
Node interface | Base type for all components |
Flow class | Pipeline execution engine wrapper |
| Port-based communication | Zero-adaptation component connections |
| Event-driven processing | Reactive 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
AStack incorporates monadic functional programming principles to achieve composability and state management:
| Principle | Implementation |
|---|---|
| Encapsulated State | Each component maintains isolated internal state |
| Chainable Operations | Components connect via ports to create fluent pipelines |
| Composable Transformations | Complex workflows built from simple, reusable units |
| Error Propagation | Errors 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
The Component class is the fundamental building block of AStack. Every component, from simple transformers to complex agents, extends this base class.
Sources: packages/core/src/component/index.ts1-40
The Component class extends TransformNode from @hlang-org/runtime and provides default port configuration:
| Member | Type | Purpose |
|---|---|---|
Component.Port | typeof Port | Static reference to Port factory from HLang |
inPort | Port.I | Default input port named 'in' |
outPort | Port.O | Default output port named 'out' |
The constructor automatically creates and attaches default ports:
Sources: packages/core/src/component/index.ts3-17
| Method | Mode | Purpose |
|---|---|---|
run(data) | Standalone | Direct component invocation, returns processed data |
_transform($i, $o) | Pipeline | Port-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
The Pipeline class provides the orchestration engine for connecting and executing components. It manages component lifecycle, port connections, and data flow.
| Method | Visibility | Purpose | Signature |
|---|---|---|---|
addComponent | Public | Register component in pipeline | (name: string, instance: Node) => void |
getComponent | Public | Retrieve registered component | (name: string) => Node | undefined |
connect | Public | Link output port to input port | (source: string, sink: string) => void |
run | Public | Execute pipeline with parameters | <T>(trigger: string, params: unknown) => Promise<T> |
from | Private | Resolve source port reference | (name: string) => Port.O |
to | Private | Resolve destination port reference | (name: string) => Port.I |
runWithBaseMode | Private | Internal 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 componenttextSplitter.out refers to the out output port of the textSplitter componentSources: 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
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:
produce() methodconsume() callbackThe 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
The Port System is AStack's mechanism for zero-adaptation component communication. Components connect directly through ports without intermediate adapters or transformation layers.
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
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
| Traditional Approach | AStack Zero-Adaptation |
|---|---|
| Requires adapter classes | Direct port connections |
| Data transformation overhead | Minimal data movement |
| Complex connection setup | Simple dot notation |
| Multiple serialization steps | Type-safe data flow |
Sources: README.md45-47 README.zh-CN.md45-47
AStack provides a set of built-in components that extend the base Component interface for common AI application patterns.
Each built-in component:
_transform and run methods)For detailed documentation of each component type, see:
Sources: README.md28-35 examples/text-splitter/index.ts2
The Tool System provides an extensible interface for integrating external capabilities into agents and components.
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
The Model Provider system abstracts LLM integrations, enabling AStack to work with multiple AI model providers through a consistent interface.
The abstraction enables:
For detailed model provider implementation, see Integrations Package and Model Provider Interface.
Sources: README.md244-259 README.zh-CN.md244-259
AStack components support two distinct execution modes, providing flexibility for different use cases.
The text-splitter example demonstrates both modes:
| Mode | Use Case | Invocation | Return Type |
|---|---|---|---|
| Standalone | Single component, quick scripts | component.run(input) | Component-defined (often synchronous) |
| Pipeline | Complex workflows, multiple components | pipeline.run(trigger, input) | Promise<T> (always asynchronous) |
Key Differences:
run() method directly_transform() method for reactive port-based executionSources: examples/text-splitter/index.ts48-76 packages/core/src/component/index.ts19-34 README.md312-326
AStack's core architecture achieves its design goals through:
| Principle | Implementation | Benefit |
|---|---|---|
| Everything is a Component | Unified Component interface | Consistency, composability |
| Zero-Adaptation | Direct port connections | Performance, simplicity |
| Layered Design | Clear separation of concerns | Maintainability, extensibility |
| Dual Execution Modes | Standalone and Pipeline support | Flexibility, ease of use |
| HLang Foundation | Flow-Based Programming runtime | Reactive patterns, event-driven |
The architecture enables developers to:
run() methodaddComponent() and connect()run() method, which manages the full lifecycleThis 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
Refresh this wiki