Extend the Runtime with Custom Service Implementations
The Smyth Runtime Environment (SRE) is built on a connector-based architecture that allows you to seamlessly integrate with virtually any external service or technology. Whether you need to add support for a new database, cloud provider, or custom API, SRE's extensible connector system makes it straightforward.
Every SRE service follows a consistent three-layer architecture that ensures modularity, security, and maintainability:
┌─────────────────────────────────────┐
│ Service Layer │ ← Registration & Management
├─────────────────────────────────────┤
│ Abstract Connector │ ← Interface Definition
├─────────────────────────────────────┤
│ Concrete Implementations │ ← Your Custom Connectors
│ [Redis] [S3] [Local] [Custom...]│
└─────────────────────────────────────┘
All SRE services follow the same organized directory structure under /src/subsystems/<category>/<servicename>.service/
:
Storage.service/
├── index.ts # Service registration
├── StorageConnector.ts # Abstract connector interface
├── connectors/ # Concrete implementations
│ ├── LocalStorage.class.ts # Local filesystem connector
│ ├── S3Storage.class.ts # AWS S3 connector
│ └── [YourCustom].class.ts # Your custom connector
└── [Service-specific helpers] # Optional utility files
index.ts
)The service entry point that registers all available connectors with the SRE runtime:
import { ConnectorService, ConnectorServiceProvider } from '@sre/Core/ConnectorsService';
import { TConnectorService } from '@sre/types/SRE.types';
import { LocalStorage } from './connectors/LocalStorage.class';
import { S3Storage } from './connectors/S3Storage.class';
export class StorageService extends ConnectorServiceProvider {
public register() {
ConnectorService.register(TConnectorService.Storage, 'LocalStorage', LocalStorage);
ConnectorService.register(TConnectorService.Storage, 'S3', S3Storage);
// Your custom connector would go here:
// ConnectorService.register(TConnectorService.Storage, 'MyCustom', MyCustomStorage);
}
}
Key Features:
TConnectorService
<Service>Connector.ts
)Defines the contract that all concrete implementations must follow:
export interface IStorageRequest {
read(resourceId: string): Promise<StorageData>;
write(resourceId: string, value: StorageData, acl?: IACL, metadata?: StorageMetadata): Promise<void>;
delete(resourceId: string): Promise<void>;
exists(resourceId: string): Promise<boolean>;
// ... additional methods
}
export abstract class StorageConnector extends SecureConnector {
// Security-aware abstract methods that implementations must provide
protected abstract read(acRequest: AccessRequest, resourceId: string): Promise<StorageData>;
protected abstract write(acRequest: AccessRequest, resourceId: string, value: StorageData, acl?: IACL, metadata?: StorageMetadata): Promise<void>;
// Public interface that handles security automatically
public requester(candidate: AccessCandidate): IStorageRequest {
return {
read: async (resourceId: string) => {
return await this.read(candidate.readRequest, resourceId);
},
write: async (resourceId: string, value: StorageData, acl?: IACL, metadata?: StorageMetadata) => {
return await this.write(candidate.writeRequest, resourceId, value, acl, metadata);
},
// ... other methods
};
}
// Resource-level access control
public abstract getResourceACL(resourceId: string, candidate: IAccessCandidate): Promise<ACL>;
}
Architecture Benefits:
AccessRequest
connectors/
)Individual connector classes that implement the abstract interface for specific technologies:
export class LocalStorage extends StorageConnector {
public name = 'LocalStorage';
private folder: string;
constructor(settings: LocalStorageConfig) {
super();
this.folder = settings.folder || path.join(os.tmpdir(), '.smyth/storage');
this.initialize();
}
// Implement all abstract methods with @SecureConnector.AccessControl decoration
@SecureConnector.AccessControl
protected async read(acRequest: AccessRequest, resourceId: string): Promise<StorageData> {
// Your implementation here
const filePath = this.getStorageFilePath(acRequest.candidate.id, resourceId);
return fs.readFileSync(filePath, 'utf-8');
}
// Access control implementation
public async getResourceACL(resourceId: string, candidate: IAccessCandidate): Promise<ACL> {
// Determine resource permissions based on your storage system
// Return appropriate ACL for the resource and candidate
}
}
Implementation Features:
@SecureConnector.AccessControl
decorator enforces access controlEvery SRE connector operates within the Candidate/ACL security model:
AccessRequest
objectsAll services follow identical patterns for:
SRE organizes connectors into logical subsystems, each serving specific runtime needs:
Subsystem | Services | Purpose |
---|---|---|
🔄 IO | Storage, VectorDB, Log, Router, NKV, CLI | External data and communication |
🧠 LLMManager | Models, Providers | AI model access |
🔐 Security | Vault, Account | Authentication and secrets |
💾 MemoryManager | Cache | Performance optimization |
🤖 AgentManager | Agents, Components | Agent execution |
Each subsystem contains multiple services, and each service supports multiple connector implementations, giving you maximum flexibility in how you architect your AI agent infrastructure.
Ready to build your first custom connector? The next sections will walk you through implementation step-by-step.
One of the most powerful features of the Smyth Runtime Environment is its modular, connector-based architecture. You can extend the SRE to support new services (like a different database, storage provider, or LLM) by creating your own custom connectors.
Developing a new connector involves three main steps:
Every connector type in SRE has a corresponding interface that defines the required methods and properties. Your first step is to create a new class that implements the appropriate interface.
For example, to create a new storage connector, you would implement IStorageConnector
.
import { IStorageConnector, TStorageConfig } from '@smythos/sre';
export class MyCustomStorageConnector implements IStorageConnector {
private settings: TStorageConfig;
constructor(settings: TStorageConfig) {
this.settings = settings;
}
async init(): Promise<void> {
// Initialization logic for your connector,
// like connecting to the database or authenticating.
console.log('MyCustomStorageConnector initialized!');
}
// ... implement all other required methods from IStorageConnector
// (e.g., read, write, delete, list)
}
Once your connector class is created, you need to register it with the SRE's ConnectorService
. This makes the runtime aware of your new connector and allows it to be selected in the configuration.
Registration is typically done in a central initialization file.
import { ConnectorService, TConnectorService } from '@smythos/sre';
import { MyCustomStorageConnector } from './my-custom-storage.connector';
// Register the new connector with a unique name
ConnectorService.register(
TConnectorService.Storage,
'MyCustomStorage', // This is the name you'll use in the config
MyCustomStorageConnector
);
After registration, you can now use your custom connector in the SRE configuration file just like any built-in connector.
// in your SRE initialization
SRE.init({
// ... other services
Storage: {
Connector: 'MyCustomStorage', // Use the name you registered
Settings: {
// any custom settings your connector needs
endpoint: 'https://my.custom.storage/api',
},
},
// ...
});