Developer Authority¶
Developer authority is the core principle that drives Arshai’s design. It’s the belief that developers should have complete control over their application’s components, lifecycle, and behavior.
The Philosophy¶
You’re the Architect: Arshai provides interfaces and building blocks, but you decide how to use them, when to use them, and whether to use them at all.
No Hidden Magic: Every component is created explicitly by you. No factories, no global state, no configuration files that hide behavior.
Interface Respect: Any component you build that implements our interfaces works seamlessly with the framework.
Traditional vs Arshai Approach¶
Traditional Framework Problems:
# Traditional approach - framework controls everything
framework = AIFramework()
framework.load_config("config.yaml")
agent = framework.create_agent("chatbot") # What's happening inside?
response = agent.chat("Hello") # How does this work?
Problems with this approach: - 🔒 Hidden complexity - You don’t know what’s being created - ⛔ Limited control - Framework decides component lifecycle - 🎭 Magic behavior - Implicit configuration and dependencies - 🧩 Poor testability - Hard to mock framework-controlled components
Arshai Approach:
# Arshai approach - you control everything
llm_config = ILLMConfig(model="gpt-4", temperature=0.7)
llm_client = OpenAIClient(llm_config) # You create it
agent = ChatbotAgent(
llm_client=llm_client, # You inject dependencies
system_prompt="Be helpful", # You configure it
)
response = await agent.process(input) # You understand the flow
Benefits: - ✅ Full visibility - See exactly what’s created and when - ✅ Complete control - You manage component lifecycle - ✅ Explicit behavior - All dependencies are visible - ✅ Easy testing - Simple to mock and test
Core Principles¶
Explicit Over Implicit¶
❌ Implicit (Hidden):
# Where do these come from? What do they do?
agent = factory.create("assistant")
agent.configure() # What's being configured?
✅ Explicit (Visible):
# Everything is clear and visible
llm_client = OpenAIClient(config)
memory = RedisMemory(url="redis://localhost")
agent = AssistantAgent(llm_client, memory, tools=[search, calculate])
Direct Control Over Abstraction¶
❌ Abstracted Away:
# Framework hides the details
settings = Settings()
agent = settings.create_agent("type", config)
# How do I customize this? When is it created?
✅ Direct Control:
# You control creation and configuration
class MyCustomAgent(BaseAgent):
def __init__(self, llm_client: ILLM, custom_param: str):
super().__init__(llm_client, "My prompt")
self.custom_param = custom_param
async def process(self, input: IAgentInput):
# Your logic, your control
return self.my_custom_logic(input)
# Create when YOU want
agent = MyCustomAgent(llm_client, "my_value")
Composition Over Configuration¶
❌ Configuration-Driven:
# config.yaml - behavior hidden in configuration
agent:
type: assistant
model: gpt-4
temperature: 0.7
memory: redis
✅ Code-Driven Composition:
# Behavior defined in code, not configuration
def create_assistant(env: str = "prod"):
if env == "prod":
llm_client = OpenAIClient(ILLMConfig(model="gpt-4"))
memory = RedisMemory(url=os.getenv("REDIS_URL"))
else:
llm_client = MockLLMClient()
memory = InMemoryStorage()
return AssistantAgent(
llm_client=llm_client,
memory=memory,
tools=[SearchTool(), CalculateTool()]
)
Dependency Injection Over Service Location¶
❌ Service Location (Anti-pattern):
class BadAgent:
def __init__(self):
# Agent finds its own dependencies
self.llm = ServiceLocator.get("llm")
self.memory = ServiceLocator.get("memory")
# Hidden dependencies!
✅ Dependency Injection:
class GoodAgent:
def __init__(self, llm: ILLM, memory: IMemoryManager):
# Dependencies are injected
self.llm = llm
self.memory = memory
# Clear, testable, controllable
Real-World Benefits¶
Testing Made Simple¶
# Easy to test with explicit dependencies
def test_agent():
# Create test doubles
mock_llm = Mock(spec=ILLM)
mock_llm.chat.return_value = {"llm_response": "Test response"}
mock_memory = Mock(spec=IMemoryManager)
# Inject mocks
agent = MyAgent(mock_llm, mock_memory)
# Test behavior
result = await agent.process(test_input)
assert result == expected
mock_llm.chat.assert_called_once()
Debugging Transparency¶
# You can see exactly what's happening
agent = DebugAgent(
llm_client=llm_client,
system_prompt="Assistant prompt"
)
# Add logging where YOU want it
if debug_mode:
agent = LoggingWrapper(agent)
# Control the flow
result = await agent.process(input)
print(f"Agent used: {agent.__class__.__name__}")
Performance Control¶
# You control performance characteristics
class OptimizedSystem:
def __init__(self):
# You decide on connection pooling
self.llm_pool = [
OpenAIClient(config) for _ in range(3)
]
# You control caching
self.cache = Redis(max_connections=10)
async def process(self, requests: List[str]):
# You control the execution strategy
tasks = [
self.process_one(req, self.llm_pool[i % 3])
for i, req in enumerate(requests)
]
return await asyncio.gather(*tasks)
Common Patterns¶
Progressive Enhancement¶
# Start simple
basic_agent = SimpleAgent(llm_client)
# Add capabilities as needed
if needs_memory:
agent_with_memory = MemoryWrapper(basic_agent, memory_manager)
else:
agent_with_memory = basic_agent
if needs_tools:
final_agent = ToolsWrapper(agent_with_memory, tools)
else:
final_agent = agent_with_memory
Custom Pipelines¶
# Build your own processing pipeline
class CustomPipeline:
def __init__(self, components: List[Component]):
self.components = components # You control the order
async def process(self, input: Any) -> Any:
result = input
for component in self.components:
result = await component.process(result)
# You can add logging, caching, etc.
return result
# Compose your pipeline
pipeline = CustomPipeline([
InputValidator(),
PreProcessor(),
MainAgent(llm_client),
PostProcessor(),
OutputFormatter()
])
What This Means for You¶
When using Arshai:
You decide when components are created
You control how they’re configured
You manage their lifecycle
You understand the flow
You own the architecture
The framework provides powerful building blocks, but you’re the architect. You decide how to use them, when to use them, and whether to use them at all.
The Bottom Line: “The framework should empower you, not constrain you.”