backend/docs/controller.md
The controller package is a central part of the backend architecture, responsible for orchestrating the lifecycle and logic of flows, assistants, tasks, subtasks, and various types of logs in the system. It acts as a high-level service layer, mediating between the database, providers, tools, and the event subscription system.
The controller package interacts with the following key packages:
database: For all persistent storage and retrieval operations.providers and tools: For LLM, tool execution, and agent chain management.graph/subscriptions: For publishing real-time events about entity changes.observability/langfuse: For tracing and logging of operations.config, docker, and templates: For configuration, container management, and prompt templating.The following diagram reflects the actual architecture of the controller package, showing all main controllers, their relationships, and integration points with other system components.
flowchart TD
subgraph Controller
FC[FlowController]
FW[FlowWorker]
AW[AssistantWorker]
TC[TaskController]
TW[TaskWorker]
STC[SubtaskController]
STW[SubtaskWorker]
subgraph LogControllers[Log Controllers]
ALC[AgentLogController]
ASLC[AssistantLogController]
MLC[MsgLogController]
SLC[SearchLogController]
TLC[TermLogController]
VSLC[VectorStoreLogController]
SC[ScreenshotController]
end
end
DB[(Database)]
LLMProviders[[ProviderController]]
ToolsExecutor[[FlowToolsExecutor]]
GraphQLSubscriptions((Subscriptions))
Obs>Observability]
Docker[[Docker]]
FC -- manages --> FW
FW -- manages --> AW
FW -- manages --> TC
TC -- manages --> TW
TW -- manages --> STC
STC -- manages --> STW
FC -- uses --> LogControllers
FW -- uses --> LogControllers
AW -- uses --> LogControllers
FC -- uses --> DB
FC -- uses --> LLMProviders
FC -- uses --> ToolsExecutor
FC -- uses --> GraphQLSubscriptions
FC -- uses --> Obs
FC -- uses --> Docker
The controller package is the main orchestrator for all user and system-initiated operations related to flows and their sub-entities. It ensures that all business logic, state transitions, and side effects (such as event publication and logging) are handled consistently and safely. It supports two main operational modes:
The controller package is built around a set of core concepts and interfaces that encapsulate the logic for managing flows, assistants, tasks, subtasks, and various types of logs. Each logical entity is represented by a controller (managing multiple entities) and a worker (managing a single entity instance).
// flows.go
type FlowController interface {
CreateFlow(
ctx context.Context,
userID int64,
input string,
prvtype provider.ProviderType,
functions *tools.Functions,
) (FlowWorker, error)
CreateAssistant(
ctx context.Context,
userID int64,
flowID int64,
input string,
useAgents bool,
prvtype provider.ProviderType,
functions *tools.Functions,
) (AssistantWorker, error)
LoadFlows(ctx context.Context) error
ListFlows(ctx context.Context) []FlowWorker
GetFlow(ctx context.Context, flowID int64) (FlowWorker, error)
StopFlow(ctx context.Context, flowID int64) error
FinishFlow(ctx context.Context, flowID int64) error
}
// flow.go
type FlowWorker interface {
GetFlowID() int64
GetUserID() int64
GetTitle() string
GetContext() *FlowContext
GetStatus(ctx context.Context) (database.FlowStatus, error)
SetStatus(ctx context.Context, status database.FlowStatus) error
AddAssistant(ctx context.Context, aw AssistantWorker) error
GetAssistant(ctx context.Context, assistantID int64) (AssistantWorker, error)
DeleteAssistant(ctx context.Context, assistantID int64) error
ListAssistants(ctx context.Context) []AssistantWorker
ListTasks(ctx context.Context) []TaskWorker
PutInput(ctx context.Context, input string) error
Finish(ctx context.Context) error
Stop(ctx context.Context) error
}
// assistant.go
type AssistantWorker interface {
GetAssistantID() int64
GetUserID() int64
GetFlowID() int64
GetTitle() string
GetStatus(ctx context.Context) (database.AssistantStatus, error)
SetStatus(ctx context.Context, status database.AssistantStatus) error
PutInput(ctx context.Context, input string, useAgents bool) error
Finish(ctx context.Context) error
Stop(ctx context.Context) error
}
// tasks.go
type TaskController interface {
CreateTask(ctx context.Context, input string, updater FlowUpdater) (TaskWorker, error)
LoadTasks(ctx context.Context, flowID int64, updater FlowUpdater) error
ListTasks(ctx context.Context) []TaskWorker
GetTask(ctx context.Context, taskID int64) (TaskWorker, error)
}
// task.go
type TaskWorker interface {
GetTaskID() int64
GetFlowID() int64
GetUserID() int64
GetTitle() string
IsCompleted() bool
IsWaiting() bool
GetStatus(ctx context.Context) (database.TaskStatus, error)
SetStatus(ctx context.Context, status database.TaskStatus) error
GetResult(ctx context.Context) (string, error)
SetResult(ctx context.Context, result string) error
PutInput(ctx context.Context, input string) error
Run(ctx context.Context) error
Finish(ctx context.Context) error
}
// subtasks.go
type SubtaskController interface {
LoadSubtasks(ctx context.Context, taskID int64, updater TaskUpdater) error
GenerateSubtasks(ctx context.Context) error
RefineSubtasks(ctx context.Context) error
PopSubtask(ctx context.Context, updater TaskUpdater) (SubtaskWorker, error)
ListSubtasks(ctx context.Context) []SubtaskWorker
GetSubtask(ctx context.Context, subtaskID int64) (SubtaskWorker, error)
}
// subtask.go
type SubtaskWorker interface {
GetMsgChainID() int64
GetSubtaskID() int64
GetTaskID() int64
GetFlowID() int64
GetUserID() int64
GetTitle() string
GetDescription() string
IsCompleted() bool
IsWaiting() bool
GetStatus(ctx context.Context) (database.SubtaskStatus, error)
SetStatus(ctx context.Context, status database.SubtaskStatus) error
GetResult(ctx context.Context) (string, error)
SetResult(ctx context.Context, result string) error
PutInput(ctx context.Context, input string) error
Run(ctx context.Context) error
Finish(ctx context.Context) error
}
The system includes seven different types of logs, each with its own controller and worker interfaces:
// alogs.go
type AgentLogController interface {
NewFlowAgentLog(ctx context.Context, flowID int64, pub subscriptions.FlowPublisher) (FlowAgentLogWorker, error)
ListFlowsAgentLog(ctx context.Context) ([]FlowAgentLogWorker, error)
GetFlowAgentLog(ctx context.Context, flowID int64) (FlowAgentLogWorker, error)
}
// alog.go
type FlowAgentLogWorker interface {
PutLog(
ctx context.Context,
initiator database.MsgchainType,
executor database.MsgchainType,
task string,
result string,
taskID *int64,
subtaskID *int64,
) (int64, error)
GetLog(ctx context.Context, msgID int64) (database.Agentlog, error)
}
// aslogs.go
type AssistantLogController interface {
NewFlowAssistantLog(
ctx context.Context, flowID int64, assistantID int64, pub subscriptions.FlowPublisher,
) (FlowAssistantLogWorker, error)
ListFlowsAssistantLog(ctx context.Context, flowID int64) ([]FlowAssistantLogWorker, error)
GetFlowAssistantLog(ctx context.Context, flowID int64, assistantID int64) (FlowAssistantLogWorker, error)
}
// aslog.go
type FlowAssistantLogWorker interface {
PutMsg(
ctx context.Context,
msgType database.MsglogType,
taskID, subtaskID *int64,
streamID int64,
thinking, msg string,
) (int64, error)
PutFlowAssistantMsg(
ctx context.Context,
msgType database.MsglogType,
thinking, msg string,
) (int64, error)
PutFlowAssistantMsgResult(
ctx context.Context,
msgType database.MsglogType,
thinking, msg, result string,
resultFormat database.MsglogResultFormat,
) (int64, error)
StreamFlowAssistantMsg(
ctx context.Context,
chunk *providers.StreamMessageChunk,
) error
UpdateMsgResult(
ctx context.Context,
msgID, streamID int64,
result string,
resultFormat database.MsglogResultFormat,
) error
}
// msglogs.go
type MsgLogController interface {
NewFlowMsgLog(ctx context.Context, flowID int64, pub subscriptions.FlowPublisher) (FlowMsgLogWorker, error)
ListFlowsMsgLog(ctx context.Context) ([]FlowMsgLogWorker, error)
GetFlowMsgLog(ctx context.Context, flowID int64) (FlowMsgLogWorker, error)
}
// msglog.go
type FlowMsgLogWorker interface {
PutMsg(
ctx context.Context,
msgType database.MsglogType,
taskID, subtaskID *int64,
streamID int64,
thinking, msg string,
) (int64, error)
PutFlowMsg(
ctx context.Context,
msgType database.MsglogType,
thinking, msg string,
) (int64, error)
PutFlowMsgResult(
ctx context.Context,
msgType database.MsglogType,
thinking, msg, result string,
resultFormat database.MsglogResultFormat,
) (int64, error)
PutTaskMsg(
ctx context.Context,
msgType database.MsglogType,
taskID int64,
thinking, msg string,
) (int64, error)
PutTaskMsgResult(
ctx context.Context,
msgType database.MsglogType,
taskID int64,
thinking, msg, result string,
resultFormat database.MsglogResultFormat,
) (int64, error)
PutSubtaskMsg(
ctx context.Context,
msgType database.MsglogType,
taskID, subtaskID int64,
thinking, msg string,
) (int64, error)
PutSubtaskMsgResult(
ctx context.Context,
msgType database.MsglogType,
taskID, subtaskID int64,
thinking, msg, result string,
resultFormat database.MsglogResultFormat,
) (int64, error)
UpdateMsgResult(
ctx context.Context,
msgID, streamID int64,
result string,
resultFormat database.MsglogResultFormat,
) error
}
// slogs.go
type SearchLogController interface {
NewFlowSearchLog(ctx context.Context, flowID int64, pub subscriptions.FlowPublisher) (FlowSearchLogWorker, error)
ListFlowsSearchLog(ctx context.Context) ([]FlowSearchLogWorker, error)
GetFlowSearchLog(ctx context.Context, flowID int64) (FlowSearchLogWorker, error)
}
// slog.go
type FlowSearchLogWorker interface {
PutLog(
ctx context.Context,
initiator database.MsgchainType,
executor database.MsgchainType,
engine database.SearchengineType,
query string,
result string,
taskID *int64,
subtaskID *int64,
) (int64, error)
GetLog(ctx context.Context, msgID int64) (database.Searchlog, error)
}
// termlogs.go
type TermLogController interface {
NewFlowTermLog(ctx context.Context, flowID int64, pub subscriptions.FlowPublisher) (FlowTermLogWorker, error)
ListFlowsTermLog(ctx context.Context) ([]FlowTermLogWorker, error)
GetFlowTermLog(ctx context.Context, flowID int64) (FlowTermLogWorker, error)
GetFlowContainers(ctx context.Context, flowID int64) ([]database.Container, error)
}
// termlog.go
type FlowTermLogWorker interface {
PutMsg(ctx context.Context, msgType database.TermlogType, msg string, containerID int64) (int64, error)
GetMsg(ctx context.Context, msgID int64) (database.Termlog, error)
GetContainers(ctx context.Context) ([]database.Container, error)
}
// vslogs.go
type VectorStoreLogController interface {
NewFlowVectorStoreLog(ctx context.Context, flowID int64, pub subscriptions.FlowPublisher) (FlowVectorStoreLogWorker, error)
ListFlowsVectorStoreLog(ctx context.Context) ([]FlowVectorStoreLogWorker, error)
GetFlowVectorStoreLog(ctx context.Context, flowID int64) (FlowVectorStoreLogWorker, error)
}
// vslog.go
type FlowVectorStoreLogWorker interface {
PutLog(
ctx context.Context,
initiator database.MsgchainType,
executor database.MsgchainType,
filter json.RawMessage,
query string,
action database.VecstoreActionType,
result string,
taskID *int64,
subtaskID *int64,
) (int64, error)
GetLog(ctx context.Context, msgID int64) (database.Vecstorelog, error)
}
// screenshots.go
type ScreenshotController interface {
NewFlowScreenshot(ctx context.Context, flowID int64, pub subscriptions.FlowPublisher) (FlowScreenshotWorker, error)
ListFlowsScreenshot(ctx context.Context) ([]FlowScreenshotWorker, error)
GetFlowScreenshot(ctx context.Context, flowID int64) (FlowScreenshotWorker, error)
}
// screenshot.go
type FlowScreenshotWorker interface {
PutScreenshot(ctx context.Context, name, url string) (int64, error)
GetScreenshot(ctx context.Context, screenshotID int64) (database.Screenshot, error)
}
// context.go
type FlowContext struct {
DB database.Querier
UserID int64
FlowID int64
FlowTitle string
Executor tools.FlowToolsExecutor
Provider providers.FlowProvider
Publisher subscriptions.FlowPublisher
TermLog FlowTermLogWorker
MsgLog FlowMsgLogWorker
Screenshot FlowScreenshotWorker
}
type TaskContext struct {
TaskID int64
TaskTitle string
TaskInput string
FlowContext
}
type SubtaskContext struct {
MsgChainID int64
SubtaskID int64
SubtaskTitle string
SubtaskDescription string
TaskContext
}
// Updater interfaces for status propagation
type FlowUpdater interface {
SetStatus(ctx context.Context, status database.FlowStatus) error
}
type TaskUpdater interface {
SetStatus(ctx context.Context, status database.TaskStatus) error
}
classDiagram
class FlowController {
+CreateFlow()
+CreateAssistant()
+LoadFlows()
+ListFlows()
+GetFlow()
+StopFlow()
+FinishFlow()
}
class FlowWorker {
+GetFlowID()
+GetUserID()
+GetTitle()
+GetContext()
+GetStatus()
+SetStatus()
+AddAssistant()
+GetAssistant()
+DeleteAssistant()
+ListAssistants()
+ListTasks()
+PutInput()
+Finish()
+Stop()
}
class AssistantWorker {
+GetAssistantID()
+GetUserID()
+GetFlowID()
+GetTitle()
+GetStatus()
+SetStatus()
+PutInput()
+Finish()
+Stop()
}
class TaskController {
+CreateTask()
+LoadTasks()
+ListTasks()
+GetTask()
}
class TaskWorker {
+GetTaskID()
+IsCompleted()
+IsWaiting()
+GetStatus()
+SetStatus()
+GetResult()
+SetResult()
+PutInput()
+Run()
+Finish()
}
class SubtaskController {
+LoadSubtasks()
+GenerateSubtasks()
+RefineSubtasks()
+PopSubtask()
+ListSubtasks()
+GetSubtask()
}
class SubtaskWorker {
+GetMsgChainID()
+GetSubtaskID()
+IsCompleted()
+IsWaiting()
+GetStatus()
+SetStatus()
+PutInput()
+Run()
+Finish()
}
class LogControllers {
<<interface>>
+NewFlowLog()
+GetFlowLog()
+ListFlowsLog()
}
class LogWorkers {
<<interface>>
+PutLog()
+GetLog()
}
FlowController --> FlowWorker : manages
FlowWorker --> AssistantWorker : manages
FlowWorker --> TaskController : contains
TaskController --> TaskWorker : manages
TaskWorker --> SubtaskController : contains
SubtaskController --> SubtaskWorker : manages
FlowController --> LogControllers : uses
LogControllers --> LogWorkers : creates
FlowWorker --> LogWorkers : uses
AssistantWorker --> LogWorkers : uses
The controller package implements a strict lifecycle and state management system for all major entities: flows, assistants, tasks, and subtasks. Each entity has a well-defined set of states, and transitions are managed through controller and worker methods, with all changes persisted to the database and broadcast via the subscription system.
Created (database.FlowStatusCreated)Running (database.FlowStatusRunning)Waiting (database.FlowStatusWaiting)Finished (database.FlowStatusFinished)Failed (database.FlowStatusFailed)Created state.Running.Waiting.Finished.Failed.SetStatus (FlowWorker), with updates persisted and events published.stateDiagram-v2
[*] --> Created: CreateFlow()
Created --> Running: PutInput() / Start Task
Running --> Waiting: Task waiting for input
Running --> Finished: All tasks completed successfully
Running --> Failed: Task failed / Error
Waiting --> Running: PutInput() / Resume Task
Waiting --> Finished: Finish()
Waiting --> Failed: Error
Finished --> [*]
Failed --> [*]
Created (database.AssistantStatusCreated)Running (database.AssistantStatusRunning)Waiting (database.AssistantStatusWaiting)Finished (database.AssistantStatusFinished)Failed (database.AssistantStatusFailed)Created state.Running.Waiting.Finished.Failed.SetStatus (AssistantWorker).stateDiagram-v2
[*] --> Created: CreateAssistant()
Created --> Running: PutInput()
Running --> Waiting: Waiting for user input
Running --> Finished: Conversation ended
Running --> Failed: Error in processing
Waiting --> Running: PutInput()
Waiting --> Finished: Finish()
Finished --> [*]
Failed --> [*]
Created (database.TaskStatusCreated)Running (database.TaskStatusRunning)Waiting (database.TaskStatusWaiting)Finished (database.TaskStatusFinished)Failed (database.TaskStatusFailed)Created state when a flow receives input.Running when execution begins.Waiting.Finished.Failed.SetStatus (TaskWorker), with updates affecting the parent flow status.stateDiagram-v2
[*] --> Created: CreateTask()
Created --> Running: Run()
Running --> Waiting: Subtask waiting for input
Running --> Finished: All subtasks completed
Running --> Failed: Subtask failed / Error
Waiting --> Running: PutInput() / Resume subtask
Waiting --> Finished: Finish()
Finished --> [*]
Failed --> [*]
Created (database.SubtaskStatusCreated)Running (database.SubtaskStatusRunning)Waiting (database.SubtaskStatusWaiting)Finished (database.SubtaskStatusFinished)Failed (database.SubtaskStatusFailed)Created state when generated by task planning.Running when execution begins.Waiting.Finished.Failed.SetStatus (SubtaskWorker), with updates affecting the parent task status.stateDiagram-v2
[*] --> Created: GenerateSubtasks()
Created --> Running: PopSubtask() / Run()
Running --> Waiting: Provider waiting for input
Running --> Finished: Provider completed successfully
Running --> Failed: Provider failed / Error
Waiting --> Running: PutInput()
Waiting --> Finished: Finish()
Finished --> [*]
Failed --> [*]
func (tw *taskWorker) SetStatus(ctx context.Context, status database.TaskStatus) error {
task, err := tw.taskCtx.DB.UpdateTaskStatus(ctx, database.UpdateTaskStatusParams{
Status: status,
ID: tw.taskCtx.TaskID,
})
if err != nil {
return fmt.Errorf("failed to set task %d status: %w", tw.taskCtx.TaskID, err)
}
subtasks, err := tw.taskCtx.DB.GetTaskSubtasks(ctx, tw.taskCtx.TaskID)
if err != nil {
return fmt.Errorf("failed to get task %d subtasks: %w", tw.taskCtx.TaskID, err)
}
tw.taskCtx.Publisher.TaskUpdated(ctx, task, subtasks)
tw.mx.Lock()
defer tw.mx.Unlock()
switch status {
case database.TaskStatusRunning:
tw.completed = false
tw.waiting = false
err = tw.updater.SetStatus(ctx, database.FlowStatusRunning)
case database.TaskStatusWaiting:
tw.completed = false
tw.waiting = true
err = tw.updater.SetStatus(ctx, database.FlowStatusWaiting)
case database.TaskStatusFinished, database.TaskStatusFailed:
tw.completed = true
tw.waiting = false
err = tw.updater.SetStatus(ctx, database.FlowStatusWaiting)
}
return err
}
The controller package manages seven distinct types of logs, each serving specific purposes in the penetration testing workflow. All logs are handled through a consistent controller/worker pattern and support real-time event publication.
Message Logs (MsgLogController/FlowMsgLogWorker)
Assistant Logs (AssistantLogController/FlowAssistantLogWorker)
Agent Logs (AgentLogController/FlowAgentLogWorker)
Search Logs (SearchLogController/FlowSearchLogWorker)
Terminal Logs (TermLogController/FlowTermLogWorker)
Vector Store Logs (VectorStoreLogController/FlowVectorStoreLogWorker)
Screenshots (ScreenshotController/FlowScreenshotWorker)
All log types follow a consistent pattern:
PutLog() or similar methodsGetLog() methodsEvery log operation publishes corresponding events:
// Example events published by log workers
pub.MessageLogAdded(ctx, msgLog)
pub.AgentLogAdded(ctx, agentLog)
pub.AssistantLogAdded(ctx, assistantLog)
pub.AssistantLogUpdated(ctx, assistantLog, isStreaming)
pub.SearchLogAdded(ctx, searchLog)
pub.TerminalLogAdded(ctx, termLog)
pub.VectorStoreLogAdded(ctx, vectorLog)
pub.ScreenshotAdded(ctx, screenshot)
Log controllers maintain thread-safe maps of workers:
// Example from MsgLogController
func (mlc *msgLogController) NewFlowMsgLog(
ctx context.Context,
flowID int64,
pub subscriptions.FlowPublisher,
) (FlowMsgLogWorker, error) {
mlc.mx.Lock()
defer mlc.mx.Unlock()
flw := NewFlowMsgLogWorker(mlc.db, flowID, pub)
mlc.flows[flowID] = flw
return flw, nil
}
The controller package is deeply integrated with external providers (LLM, tools), the tools execution layer, and the event subscription system. This integration is essential for orchestrating complex flows, executing tasks and subtasks, and providing real-time updates to clients.
providers.ProviderController interface to create and manage provider instances for each flow and assistant.FlowProvider, and assistants have their own AssistantProvider.tools.FlowToolsExecutor is created for each flow and is responsible for executing tool calls within the flow context.subscriptions.FlowPublisher is created for each flow and publishes all significant events.All dependencies are injected through constructor parameters and context objects:
type flowWorkerCtx struct {
db database.Querier
cfg *config.Config
docker docker.DockerClient
provs providers.ProviderController
subs subscriptions.SubscriptionsController
flowProviderControllers
}
type flowProviderControllers struct {
mlc MsgLogController
aslc AssistantLogController
alc AgentLogController
slc SearchLogController
tlc TermLogController
vslc VectorStoreLogController
sc ScreenshotController
}
flowchart LR
subgraph Controllers
FC[FlowController]
FW[FlowWorker]
AW[AssistantWorker]
LogCtrl[Log Controllers]
end
subgraph External
DB[(Database)]
Providers[AI Providers]
Tools[Tools Executor]
Subs[Subscriptions]
Docker[Docker]
end
FC --> FW
FW --> AW
FC --> LogCtrl
FW --> LogCtrl
AW --> LogCtrl
Controllers --> DB
Controllers --> Providers
Controllers --> Tools
Controllers --> Subs
Controllers --> Docker
The controller package is designed for safe concurrent operation in a multi-user, multi-flow environment. All controllers and workers use mutexes to ensure thread safety for all mutable state.
*sync.Mutex or *sync.RWMutex to guard access to internal maps and state.type flowController struct {
db database.Querier
mx *sync.Mutex
flows map[int64]FlowWorker
// ... other dependencies ...
}
func (fc *flowController) CreateFlow(...) (FlowWorker, error) {
fc.mx.Lock()
defer fc.mx.Unlock()
// ... mutate fc.flows ...
}
type flowMsgLogWorker struct {
db database.Querier
mx *sync.Mutex
flowID int64
pub subscriptions.FlowPublisher
}
func (mlw *flowMsgLogWorker) PutMsg(...) (int64, error) {
mlw.mx.Lock()
defer mlw.mx.Unlock()
// ... perform log operation ...
}
FlowContext, TaskContext, SubtaskContext) pass dependencies down the hierarchy.FlowWorker and AssistantWorker use goroutines and channels to process input asynchronously.// Assistant log worker supports streaming for real-time chat
func (aslw *flowAssistantLogWorker) workerMsgUpdater(
msgID, streamID int64,
ch chan *providers.StreamMessageChunk,
) {
// Processes streaming chunks in background goroutine
for chunk := range ch {
// Update database and publish events in real-time
processChunk(chunk)
}
}
The controller package is designed for extensibility, robust error handling, and safe integration into larger systems.
To add a new log type, follow this pattern:
LogController interface and LogWorker interfaceflowProviderControllers structfmt.Errorf.logrus and propagated up the call stack.Failed states.wrapErrorEndSpan utility provides consistent error handling with observability.func wrapErrorEndSpan(ctx context.Context, span langfuse.Span, msg string, err error) error {
logrus.WithContext(ctx).WithError(err).Error(msg)
err = fmt.Errorf("%s: %w", msg, err)
span.End(
langfuse.WithEndSpanStatus(err.Error()),
langfuse.WithSpanLevel(langfuse.ObservationLevelError),
)
return err
}
Finish() methods.The controller package provides a robust foundation for managing complex AI-driven penetration testing workflows while maintaining reliability, observability, and extensibility.