Lightweight conversation state machine for RCS agents
npm install @rcs-lang/csmA lightweight, TypeScript-first state machine library designed specifically for RCS conversational agents. CSM provides a simple, performant way to manage conversation flow state across stateless HTTP requests.
CSM (Conversation State Machine) is built to:
- Execute conversation flows defined in RCL documents
- Manage state across stateless HTTP requests in serverless environments
- Provide simple machine-to-machine transitions for flow composition
- Generate compact, URL-safe state representations
- Handle conversation context and message interpolation
- ðŠķ Lightweight: ~3KB minified, designed for serverless
- ð State Persistence: Serialize/deserialize state between requests
- ð URL-Safe: Compact state representation for URL parameters
- ðŊ Simple API: Single callback for all state transitions
- ð§Đ Composable: Connect multiple flows into complex agents
- ðĶ Zero Dependencies: No external runtime dependencies
- ð Fast: Optimized for request/response cycle performance
``bash`
npm install @rcs-lang/csm
`typescript
import { ConversationalAgent, FlowDefinition } from '@rcs-lang/csm';
// Define your flow (usually generated from RCL)
const coffeeFlow: FlowDefinition = {
id: 'OrderFlow',
initial: 'Welcome',
states: {
Welcome: {
transitions: [
{ pattern: 'Order Coffee', target: 'ChooseSize' },
{ pattern: 'View Menu', target: 'ShowMenu' }
]
},
ChooseSize: {
transitions: [
{ pattern: 'Small', target: 'ChooseDrink', context: { size: 'small', price: 3.50 } },
{ pattern: 'Medium', target: 'ChooseDrink', context: { size: 'medium', price: 4.50 } }
]
}
}
};
// Create agent with state change handler
const agent = new ConversationalAgent({
id: 'CoffeeBot',
onStateChange: async (event) => {
console.log(Entering state: ${event.state});
// Send message, log analytics, etc.
await sendMessage(event.context.userId, messages[event.state]);
}
});
// Add flows
agent.addFlow(coffeeFlow);
// Process user input
const response = await agent.processInput('Order Coffee');
// response.state = 'ChooseSize'
// response.machine = 'OrderFlow'
// Serialize for next request
const stateHash = agent.toURLHash();
// "Q29mZmVlQm90Ok9yZGVyRmxvdzpDaG9vc2VTaXplOnt9"
// Restore in next request
const restoredAgent = ConversationalAgent.fromURLHash(stateHash, { onStateChange });
`
The main class for managing conversation state across multiple flows.
`typescript
class ConversationalAgent {
constructor(options: AgentOptions);
// Flow management
addFlow(flow: FlowDefinition): void;
removeFlow(flowId: string): void;
// State processing
processInput(input: string): Promise
// Serialization
toURLHash(): string;
static fromURLHash(hash: string, options: AgentOptions): ConversationalAgent;
// State access
getCurrentState(): AgentState;
getContext(): Context;
updateContext(updates: Partial
}
`
Structure for defining a conversation flow (typically generated from RCL).
`typescript
interface FlowDefinition {
id: string;
initial: string;
states: Record
}
interface StateDefinition {
// State is transient if it has a single transition with no pattern
transitions: Transition[];
// Metadata for the state
meta?: {
messageId?: string;
transient?: boolean;
};
}
interface Transition {
pattern?: string; // User input pattern to match
target: string; // Target state or "machine:FlowId"
context?: Record
condition?: string; // Optional JS condition
}
`
Configuration for the agent.
`typescript
interface AgentOptions {
id: string;
// Single callback for all state changes
onStateChange: (event: StateChangeEvent) => Promise
// Optional configuration
serialization?: {
compress?: boolean; // Use compression for URL hash
encryption?: { // Optional encryption
key: string;
algorithm?: string;
};
};
// Error handling
onError?: (error: Error, context: ErrorContext) => void;
}
`
Event passed to the state change handler.
`typescript
interface StateChangeEvent {
// State information
agent: string;
machine: string;
state: string;
previousState?: string;
// Transition information
trigger: 'input' | 'transient' | 'machine' | 'restore';
input?: string;
// Context
context: Context;
// Timestamp
timestamp: number;
}
`
Instead of callbacks per state, CSM uses a single onStateChange callback. This design:
- Simplifies integration (one place to handle all state changes)
- Works better with serverless (single function endpoint)
- Easier to add cross-cutting concerns (logging, analytics)
- Reduces configuration complexity
States with a single unconditional transition are automatically transient:
`typescript`
ProcessPayment: {
transitions: [
{ target: 'OrderComplete' } // No pattern = transient
]
}
Transition to another flow using machine: prefix:`typescript`
transitions: [
{ pattern: 'Contact Support', target: 'machine:ContactFlow' }
]
Compact, URL-safe encoding:
``
base64url({
a: agentId, // agent
m: machineId, // machine (flow)
s: stateId, // state
c: context // context object
})
`typescript
export async function handleMessage(request: Request) {
const { stateHash, userInput } = await request.json();
// Restore agent state
const agent = stateHash
? ConversationalAgent.fromURLHash(stateHash, {
id: 'CoffeeBot',
onStateChange: async (event) => {
// Log state change
await logAnalytics(event);
// Get message for state
const message = getMessageForState(event.state);
// Store response to send back
response.message = message;
}
})
: createNewAgent();
// Process input
const result = await agent.processInput(userInput);
// Return response with new state
return Response.json({
message: response.message,
stateHash: agent.toURLHash(),
suggestions: getSuggestionsForState(result.state)
});
}
`
`typescript
app.post('/conversation', async (req, res) => {
const agent = createOrRestoreAgent(req.body.stateHash);
const result = await agent.processInput(req.body.input);
res.json({
state: result,
hash: agent.toURLHash()
});
});
`
`typescript
// Import reusable flows
import { ContactSupportFlow } from '@rcs-lang/common-flows';
// Define custom flow
const customFlow: FlowDefinition = {
id: 'MainMenu',
initial: 'Welcome',
states: {
Welcome: {
transitions: [
{ pattern: 'Support', target: 'machine:ContactSupport' }
]
}
}
};
// Compose agent
const agent = new ConversationalAgent({ id: 'MyBot', onStateChange });
agent.addFlow(customFlow);
agent.addFlow(ContactSupportFlow);
``
- Minimal Overhead: ~1ms to process typical state transition
- Compact State: Average URL hash ~100-200 characters
- Memory Efficient: No persistence between requests
- Fast Serialization: Optimized JSON encoding
- Pattern Caching: Compiled patterns cached per flow
See CONTRIBUTING.md for development setup and guidelines.
MIT