TypeScript SDK Guide
Active Development
The SDK is under active development. Pin to a specific release for production use.
The TypeScript SDK lets you build Aether agents using modern async/await with full TypeScript types. It communicates with the Hub over gRPC via @grpc/grpc-js.
Installation
From npm
npm install @aether/agent-sdkFrom Source
git clone https://github.com/amansrivastava/hacky-automation.git
cd hacky-automation/sdks/typescript
npm install
npm run buildRequires: Node.js 16+
Configuration
Environment Variables
AETHER_AGENT_ID=my-agent-001
AETHER_AGENT_SECRET=sk-secret
AETHER_AGENT_NAME="My Agent"
AETHER_HUB_ADDRESS=localhost:50051
AETHER_VERSION=1.0.0
AETHER_HEARTBEAT_INTERVAL=30
AETHER_RECONNECT_DELAY=5
AETHER_MAX_RECONNECT_ATTEMPTS=-1AgentConfig Reference
| Option | Type | Default | Description |
|---|---|---|---|
agentId | string | Required | Unique identifier (e.g. pm_sarah) |
agentSecret | string | Required | Authentication secret |
agentName | string | Required | Human-readable name |
version | string | "0.1.0" | Agent version |
hubAddress | string | "localhost:50051" | Hub gRPC endpoint |
capabilities | object | {} | Key-value capability flags |
metadata | object | {} | Arbitrary key-value metadata |
heartbeatInterval | number | 30 | Seconds between heartbeats |
reconnectDelay | number | 5 | Seconds before reconnect attempt |
maxReconnectAttempts | number | -1 | Max attempts (-1 = infinite) |
Quickstart
Create Your Agent
Extend AetherAgent and override handleTask:
import { AetherAgent, Task, TaskResult } from '@aether/agent-sdk';
class MyAgent extends AetherAgent {
agentId = 'my-agent-001';
capabilities = ['analysis', 'processing'];
async handleTask(task: Task): Promise<TaskResult> {
console.log(`Processing task ${task.taskId}: ${task.description}`);
try {
const result = `Processed: ${task.description}`;
return {
taskId: task.taskId,
success: true,
result: Buffer.from(result),
metrics: { processing_time: '0.5s' },
};
} catch (err) {
return {
taskId: task.taskId,
success: false,
errorMessage: String(err),
};
}
}
}
const agent = new MyAgent({
agentId: 'my-agent-001',
agentSecret: process.env.AGENT_SECRET ?? '',
agentName: 'My Example Agent',
hubAddress: process.env.HUB_ADDRESS ?? 'localhost:50051',
});
agent.run();Loading Config from Environment
import { AetherAgent, loadConfigFromEnv, Task, TaskResult } from '@aether/agent-sdk';
class MyAgent extends AetherAgent {
agentId = 'my-agent';
async handleTask(task: Task): Promise<TaskResult> {
// your logic
return { taskId: task.taskId, success: true, result: Buffer.from('done') };
}
}
const config = loadConfigFromEnv({
agentId: 'my-agent',
agentName: 'My Agent',
});
new MyAgent(config).run();Start, Stop, Run
// Start: connect + register (non-blocking)
await agent.start();
// Stop: unregister + disconnect
await agent.stop();
// Run: start + block until stopped (handles SIGINT/SIGTERM)
await agent.run();Hub Services
Access Hub-proxied services through this.hub inside handleTask.
LLM
const response = await this.hub.llm.chat({
messages: [
{ role: 'system', content: 'You are a helpful assistant.' },
{ role: 'user', content: task.payload.toString() },
],
model: 'gpt-4o-mini', // Hub may override based on trust level
temperature: 0.7,
maxTokens: 1000,
});
console.log(response.content);
console.log(`Tokens used: ${response.usage.totalTokens}`);Tools
// List available tools
const tools = await this.hub.tools.list();
console.log(tools.map(t => t.name));
// Execute a tool
const result = await this.hub.tools.execute('add_comment', {
issue_id: '123',
comment: 'Review complete',
});
const output = JSON.parse(result.resultJson);Memory
// Store data
await this.hub.memory.store('my-config', { theme: 'dark' }, 'agent', 3600);
// Retrieve data
const response = await this.hub.memory.retrieve('my-config', 'agent');
if (response.success) {
const config = JSON.parse(response.valueJson);
}Messaging
// Publish to NATS subject
await this.hub.messaging.publish('agent.status', {
agent_id: this.agentId,
status: 'processing',
});
// Request-reply
const response = await this.hub.messaging.request(
'workflow.status',
{ workflow_id: '123' },
10, // timeout in seconds
);Example: Code Reviewer Agent
import { AetherAgent, Task, TaskResult } from '@aether/agent-sdk';
class CodeReviewerAgent extends AetherAgent {
agentId = 'dev_ts_reviewer';
capabilities = ['code_review', 'typescript'];
async handleTask(task: Task): Promise<TaskResult> {
const diff = task.payload?.toString() ?? task.description;
const response = await this.hub.llm.chat({
messages: [
{
role: 'system',
content: 'You are an expert code reviewer. Provide concise, constructive feedback.',
},
{ role: 'user', content: `Review this diff:\n\n${diff}` },
],
temperature: 0.2,
});
if (!response.success) {
return {
taskId: task.taskId,
success: false,
errorMessage: `LLM call failed: ${response.errorMessage}`,
};
}
// Notify other agents
await this.hub.messaging.publish('review.completed', {
task_id: task.taskId,
agent: this.agentId,
});
return {
taskId: task.taskId,
success: true,
result: Buffer.from(response.content),
metrics: {
model: response.model,
tokens: String(response.usage.totalTokens),
},
};
}
}
new CodeReviewerAgent({
agentId: 'dev_ts_reviewer',
agentSecret: process.env.AGENT_SECRET ?? '',
agentName: 'TypeScript Code Reviewer',
hubAddress: process.env.HUB_ADDRESS ?? 'localhost:50051',
capabilities: { code_review: 'true', typescript: 'true' },
}).run();Example: Custom Data Processor
import { AetherAgent, Task, TaskResult } from '@aether/agent-sdk';
class DataProcessorAgent extends AetherAgent {
agentId = 'data_processor_ts';
capabilities = ['data_processing'];
async handleTask(task: Task): Promise<TaskResult> {
let data: unknown;
try {
data = JSON.parse(task.payload.toString());
} catch {
data = { raw: task.description };
}
const response = await this.hub.llm.chat({
messages: [
{ role: 'system', content: 'Classify and summarize this data as JSON.' },
{ role: 'user', content: JSON.stringify(data) },
],
temperature: 0.0,
});
// Persist analysis
await this.hub.memory.store(
`analysis-${task.taskId}`,
{ analysis: response.content },
'task',
3600,
);
// Publish completion event
await this.hub.messaging.publish('task.completed', {
task_id: task.taskId,
status: 'success',
});
return {
taskId: task.taskId,
success: true,
result: Buffer.from(response.content),
};
}
}
new DataProcessorAgent({
agentId: 'data_processor_ts',
agentSecret: process.env.AGENT_SECRET ?? '',
agentName: 'Data Processor',
hubAddress: process.env.HUB_ADDRESS ?? 'localhost:50051',
}).run();Error Handling
handleTask should never throw — catch errors and return a failure TaskResult:
async handleTask(task: Task): Promise<TaskResult> {
try {
// ... your logic
return { taskId: task.taskId, success: true, result: Buffer.from('done') };
} catch (err) {
return {
taskId: task.taskId,
success: false,
errorMessage: err instanceof Error ? err.message : String(err),
};
}
}The SDK handles reconnection automatically. Configure limits with maxReconnectAttempts (default -1 = infinite).
Deployment
Standalone Node.js
cd examples/code-reviewer
npm install
npm startDocker
FROM node:20-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --production
COPY dist/ ./dist/
CMD ["node", "dist/agent.js"]docker build -t my-ts-agent .
docker run -e HUB_ADDRESS=hub:50051 -e AGENT_SECRET=sk-xxx my-ts-agentDocker Compose (with Aether)
services:
my-ts-agent:
build: ./my-ts-agent
environment:
HUB_ADDRESS: goway:9090
AGENT_SECRET: ${AGENT_SECRET}
depends_on:
- goway
restart: unless-stoppedKubernetes
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-ts-agent
spec:
replicas: 1
selector:
matchLabels:
app: my-ts-agent
template:
metadata:
labels:
app: my-ts-agent
spec:
containers:
- name: agent
image: my-ts-agent:latest
env:
- name: HUB_ADDRESS
value: "aether-hub:9090"
- name: AETHER_AGENT_SECRET
valueFrom:
secretKeyRef:
name: aether-secrets
key: agent-secretAPI Reference
AetherAgent
Base class for all agents. Extend and override handleTask.
Abstract properties:
agentId: string— must be overridden with a unique identifier
Optional properties:
capabilities?: string[]— array of capability tags
Methods:
| Method | Description |
|---|---|
handleTask(task): Promise<TaskResult> | Override this — your task logic |
start(): Promise<void> | Connect and register with Hub |
stop(): Promise<void> | Unregister and disconnect |
run(): Promise<void> | Start and block until stopped |
Properties:
this.hub: AgentClient— access to Hub services (available afterstart())
AgentClient
Low-level gRPC client. Accessible via this.hub.
| Method | Description |
|---|---|
connect() | Establish gRPC connection |
disconnect() | Close connection |
register() | Register with Hub → AgentRegistration |
unregister() | Unregister from Hub |
sendHeartbeat(status?) | Send heartbeat |
streamTasks() | AsyncIterableIterator<Task> |
sendTaskResult(result) | Submit task result |
Service proxies:
hub.llm—LLMProxyhub.tools—ToolsProxyhub.memory—MemoryProxyhub.messaging—MessagingProxy
Key Types
interface Task {
taskId: string;
description: string;
payload: Buffer;
metadata: Record<string, string>;
}
interface TaskResult {
taskId: string;
success: boolean;
result?: Buffer;
errorMessage?: string;
metrics?: Record<string, string>;
}
interface AgentConfig {
agentId: string;
agentSecret: string;
agentName: string;
version?: string;
hubAddress?: string;
capabilities?: Record<string, string>;
metadata?: Record<string, string>;
heartbeatInterval?: number;
reconnectDelay?: number;
maxReconnectAttempts?: number;
}Development
cd sdks/typescript
npm install # Install dependencies
npm run build # Compile TypeScript → dist/
npm run watch # Watch mode for development
npm run lint # ESLint
npm run format # Prettier
npm test # Run tests (Jest)Troubleshooting
Error: 14 UNAVAILABLE: Connection refused
The Hub gRPC port is not reachable. Check:
- Hub is running (
docker-compose ps goway) hubAddresspoints to the correct host/port (default9090for gRPC)
Error: 16 UNAUTHENTICATED
The agentSecret does not match the secret on the agent record in the Hub. Verify via the Aether API (GET /api/agents/:id).
Proto-generated types are stale
The TypeScript SDK includes pre-generated gRPC stubs in src/proto/. If the proto definition changes in goway/proto/, regenerate them:
cd sdks/typescript
npm run proto-genCannot find module '@aether/agent-sdk'
When using from source, build first:
cd sdks/typescript
npm run buildThen link or reference the dist/ output directly.
