chore: install openagent opencode

Signed-off-by: Dmytro Stanchiev <git@dmytros.dev>
This commit is contained in:
2026-04-07 11:31:26 -04:00
parent b4c03ff25e
commit c2263602c4
204 changed files with 38010 additions and 0 deletions

View File

@@ -0,0 +1,40 @@
<!-- Context: openagents-repo/lifecycle | Priority: low | Version: 1.0 | Updated: 2026-02-15 -->
# Plugin Lifecycle & Packaging
## File Structure for Complex Plugins
For larger plugins, follow this recommended structure:
```
my-plugin/
├── .claude-plugin/
│ └── plugin.json # Manifest (required for packaging)
├── commands/ # Custom slash commands
├── agents/ # Custom agents
├── hooks/ # Event handlers
└── README.md # Documentation
```
## The Manifest (`plugin.json`)
```json
{
"name": "my-plugin",
"description": "A custom plugin",
"version": "1.0.0",
"author": {
"name": "Your Name"
}
}
```
The `name` becomes the namespace prefix for commands: `/my-plugin:command`.
## SDK Access
Plugins have full access to the OpenCode SDK via `context.client`. This allows:
- Sending prompts programmatically: `client.session.prompt()`
- Managing sessions: `client.session.list()`, `client.session.get()`
- Showing UI elements: `client.tui.showToast()`
- Appending to prompt: `client.tui.appendPrompt()`

View File

@@ -0,0 +1,60 @@
<!-- Context: openagents-repo/overview | Priority: low | Version: 1.0 | Updated: 2026-02-15 -->
# OpenCode Plugins Overview
OpenCode plugins are JavaScript or TypeScript modules that hook into **25+ events** across the entire OpenCode lifecycle—from when you type a prompt, to when tools execute, to when sessions complete.
## Key Concepts
- **Zero-Config**: No build step or compilation required. Just drop `.ts` or `.js` files into the plugin folder.
- **Middleware Pattern**: Plugins subscribe to events and execute logic, similar to Express.js middleware.
- **Access**: Plugins receive a `context` object with:
- `project`: Current project metadata.
- `client`: OpenCode SDK client for programmatic control.
- `$`: Bun's shell API for running commands.
- `directory`: Current working directory.
- `worktree`: Git worktree path.
## Plugin Registration
OpenCode looks for plugins in:
1. **Project-level**: `.opencode/plugin/` (project root)
2. **Global**: `~/.config/opencode/plugin/` (home directory)
## Basic Structure
```typescript
export const MyPlugin = async (context) => {
const { project, client, $, directory, worktree } = context;
return {
event: async ({ event }) => {
// Handle events here
}
};
};
```
Each exported function becomes a separate plugin instance. The name of the export is used as the plugin name.
## Build and Development
OpenCode plugins are typically written in TypeScript and bundled into a single JavaScript file for execution.
### Build Command
Use Bun to bundle the plugin into the `dist` directory:
```bash
bun build src/index.ts --outdir dist --target bun --format esm
```
The output will be a single file (e.g., `./index.js`) containing all dependencies.
### Development Workflow
1. **Source Code**: Write your plugin in `src/index.ts`.
2. **Bundle**: Run the build command to generate `dist/index.js`.
3. **Load**: Point OpenCode to the bundled file or the directory containing the manifest.
4. **Watch Mode**: For rapid development, use the `--watch` flag with Bun build:
```bash
bun build src/index.ts --outdir dist --target bun --format esm --watch
```

View File

@@ -0,0 +1,46 @@
<!-- Context: openagents-repo/agents | Priority: low | Version: 1.0 | Updated: 2026-02-15 -->
# Custom Agents in OpenCode
Plugins can register custom AI agents that have specific roles, instructions, and toolsets.
## Agent Definition
Custom agents are configured in the plugin's `config` function.
```typescript
export const registerCustomAgents = (config) => {
return {
...config,
agents: [
{
name: "my-helper",
description: "A friendly assistant for this project",
instructions: "You are a helpful assistant. Use your tools to help the user.",
model: "claude-3-5-sonnet-latest", // Specify the model
tools: ["say_hello", "read", "write"] // Reference built-in or custom tools
}
]
};
};
```
## Integrating into Plugin
The `config` method in the plugin return object is used to register agents.
```typescript
export const MyPlugin: Plugin = async (context) => {
return {
config: async (currentConfig) => {
return registerCustomAgents(currentConfig);
},
// ... other properties
};
};
```
## Agent Capabilities
- **Model Choice**: You can select specific models for different agents.
- **Scoped Tools**: Limit what tools an agent can use to ensure safety or focus.
- **System Instructions**: Define the "personality" and rules for the agent.

View File

@@ -0,0 +1,44 @@
<!-- Context: openagents-repo/events | Priority: low | Version: 1.0 | Updated: 2026-02-15 -->
# OpenCode Plugin Events
OpenCode fires over 25 events that you can hook into. These are categorized below:
## Command Events
- `command.executed`: Fired when a user or plugin runs a command.
## File Events
- `file.edited`: Fired when a file is modified via OpenCode tools.
- `file.watcher.updated`: Fired when the file watcher detects changes.
## Message Events (Read-Only)
- `message.updated`: Fired when a message in the session updates.
- `message.part.updated`: Fired when individual parts of a message update.
- `message.part.removed`: Fired when a part is removed.
- `message.removed`: Fired when entire message is removed.
## Session Events
- `session.created`: New session started.
- `session.updated`: Session state changed.
- `session.idle`: Session completed (no more activity expected).
- `session.status`: Session status changed.
- `session.error`: Error occurred in session.
- `session.compacted`: Session was compacted (context summarized).
## Tool Events (Interception)
- `tool.execute.before`: Fired before a tool runs. **Can block execution** by throwing an error.
- `tool.execute.after`: Fired after a tool completes with result.
## TUI Events
- `tui.prompt.append`: Text appended to prompt input.
- `tui.command.execute`: Command executed from TUI.
- `tui.toast.show`: Toast notification shown.
## Mapping from Claude Code Hooks
| Claude Hook | OpenCode Event |
|---|---|
| PreToolUse | tool.execute.before |
| PostToolUse | tool.execute.after |
| UserPromptSubmit | message.* events |
| SessionEnd | session.idle |

View File

@@ -0,0 +1,598 @@
<!-- Context: openagents-repo/events_skills | Priority: low | Version: 1.0 | Updated: 2026-02-15 -->
# OpenCode Events: Skills Plugin Implementation
## Overview
This document explains how the OpenCode Skills Plugin uses event hooks (`tool.execute.before` and `tool.execute.after`) to implement skill delivery and output enhancement. This is a practical example of the event system described in `events.md`.
---
## Event Hooks Used
### tool.execute.before
**Event Type:** Tool Execution Interception
**When it fires:** Before a tool function executes
**Purpose in Skills Plugin:** Inject skill content into the conversation
**Implementation:**
```typescript
const beforeHook = async (input: any, output: any) => {
// Check if this is a skill tool
if (input.tool.startsWith("skills_")) {
// Look up skill from map
const skill = skillMap.get(input.tool)
if (skill) {
// Inject skill content as silent prompt
await ctx.client.session.prompt({
path: { id: input.sessionID },
body: {
agent: input.agent,
noReply: true, // Don't trigger AI response
parts: [
{
type: "text",
text: `📚 Skill: ${skill.name}\nBase directory: ${skill.fullPath}\n\n${skill.content}`,
},
],
},
})
}
}
}
```
**Why use this hook?**
- Runs before tool execution, perfect for context injection
- Can access tool name and session ID
- Can inject content without triggering AI response
- Skill content persists in conversation history
**Input Parameters:**
- `input.tool` - Tool name (e.g., "skills_brand_guidelines")
- `input.sessionID` - Current session ID
- `input.agent` - Agent name that called the tool
- `output.args` - Tool arguments
**What you can do:**
- ✅ Inject context (skill content)
- ✅ Validate inputs
- ✅ Preprocess arguments
- ✅ Log tool calls
- ✅ Implement security checks
**What you can't do:**
- ❌ Modify tool output (tool hasn't run yet)
- ❌ Access tool results
---
### tool.execute.after
**Event Type:** Tool Execution Interception
**When it fires:** After a tool function completes
**Purpose in Skills Plugin:** Enhance output with visual feedback
**Implementation:**
```typescript
const afterHook = async (input: any, output: any) => {
// Check if this is a skill tool
if (input.tool.startsWith("skills_")) {
// Look up skill from map
const skill = skillMap.get(input.tool)
if (skill && output.output) {
// Add emoji title for visual feedback
output.title = `📚 ${skill.name}`
}
}
}
```
**Why use this hook?**
- Runs after tool execution, perfect for output enhancement
- Can modify output properties
- Can add visual feedback (emoji titles)
- Can implement logging/analytics
**Input Parameters:**
- `input.tool` - Tool name (e.g., "skills_brand_guidelines")
- `input.sessionID` - Current session ID
- `output.output` - Tool result/output
- `output.title` - Output title (can be modified)
**What you can do:**
- ✅ Modify output
- ✅ Add titles/formatting
- ✅ Log completion
- ✅ Add analytics
- ✅ Transform results
**What you can't do:**
- ❌ Modify tool arguments (already executed)
- ❌ Prevent tool execution (already happened)
---
## Event Lifecycle in Skills Plugin
```
┌─────────────────────────────────────────────────────────────────┐
│ AGENT CALLS SKILL TOOL │
│ (e.g., skills_brand_guidelines) │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ EVENT: tool.execute.before fires │
│ │
│ Hook Function: beforeHook(input, output) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 1. Check: input.tool.startsWith("skills_") │ │
│ │ 2. Lookup: skillMap.get(input.tool) │ │
│ │ 3. Inject: ctx.client.session.prompt({ │ │
│ │ path: { id: input.sessionID }, │ │
│ │ body: { │ │
│ │ agent: input.agent, │ │
│ │ noReply: true, │ │
│ │ parts: [{ type: "text", text: skill.content }] │ │
│ │ } │ │
│ │ }) │ │
│ │ 4. Result: Skill content added to conversation │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ Effect: Skill content persists in conversation history │
│ No AI response triggered (noReply: true) │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ TOOL.EXECUTE() RUNS │
│ │
│ async execute(args, toolCtx) { │
│ return `Skill activated: ${skill.name}` │
│ } │
│ │
│ Effect: Minimal confirmation returned │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ EVENT: tool.execute.after fires │
│ │
│ Hook Function: afterHook(input, output) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 1. Check: input.tool.startsWith("skills_") │ │
│ │ 2. Lookup: skillMap.get(input.tool) │ │
│ │ 3. Verify: output.output exists │ │
│ │ 4. Enhance: output.title = `📚 ${skill.name}` │ │
│ │ 5. Result: Output title modified with emoji │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ Effect: Visual feedback added to output │
│ Could add logging/analytics here │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ RESULT RETURNED TO AGENT │
│ │
│ - Tool confirmation message │
│ - Skill content in conversation history │
│ - Enhanced output with emoji title │
│ - Agent can now use skill content in reasoning │
└─────────────────────────────────────────────────────────────────┘
```
---
## Why Hooks Instead of Embedded Logic?
### Problem: Embedded Delivery (Anti-Pattern)
```typescript
// ❌ OLD: Skill delivery inside tool.execute()
async execute(args, toolCtx) {
const sendSilentPrompt = (text: string) =>
ctx.client.session.prompt({...})
await sendSilentPrompt(`The "${skill.name}" skill is loading...`)
await sendSilentPrompt(`Base directory: ${skill.fullPath}\n\n${skill.content}`)
return `Launching skill: ${skill.name}`
}
```
**Issues:**
1. **Tight Coupling**: Tool logic and delivery are inseparable
2. **Hard to Test**: Can't test tool without testing delivery
3. **Violates SOLID**: Single Responsibility Principle broken
4. **No Reusability**: Delivery logic can't be extracted
5. **Difficult to Monitor**: Can't track delivery separately
---
### Solution: Hook-Based Delivery (Best Practice)
```typescript
// ✅ NEW: Separated concerns using hooks
// Tool: Minimal and focused
async execute(args, toolCtx) {
return `Skill activated: ${skill.name}`
}
// Hook: Handles delivery
const beforeHook = async (input, output) => {
if (input.tool.startsWith("skills_")) {
const skill = skillMap.get(input.tool)
if (skill) {
await ctx.client.session.prompt({...})
}
}
}
```
**Benefits:**
1.**Loose Coupling**: Tool and delivery are independent
2.**Easy to Test**: Each component tested separately
3.**SOLID Compliant**: Single Responsibility Principle
4.**Reusable**: Hooks can be composed with other plugins
5.**Monitorable**: Can add logging/analytics independently
---
## Skill Lookup Map: Performance Optimization
### Why a Map?
The skill lookup map enables O(1) access instead of O(n) search:
```typescript
// ✅ EFFICIENT: O(1) lookup
const skillMap = new Map<string, Skill>()
for (const skill of skills) {
skillMap.set(skill.toolName, skill)
}
const beforeHook = async (input, output) => {
if (input.tool.startsWith("skills_")) {
const skill = skillMap.get(input.tool) // O(1) constant time
if (skill) {
// Use skill
}
}
}
```
### Performance Impact
| Number of Skills | Array Search (O(n)) | Map Lookup (O(1)) | Speedup |
|------------------|-------------------|------------------|---------|
| 10 | 10 comparisons | 1 lookup | 10x |
| 100 | 100 comparisons | 1 lookup | 100x |
| 1000 | 1000 comparisons | 1 lookup | 1000x |
**Conclusion:** Map lookup is essential for scalability
---
## Integration with OpenCode Event System
### Event Mapping
| OpenCode Event | Skills Plugin Hook | Purpose |
|---|---|---|
| `tool.execute.before` | `beforeHook` | Skill content injection |
| `tool.execute.after` | `afterHook` | Output enhancement |
### Plugin Return Object
```typescript
return {
// Custom tools
tool: tools,
// Hook: Runs before tool execution
"tool.execute.before": beforeHook,
// Hook: Runs after tool execution
"tool.execute.after": afterHook,
}
```
**Key Points:**
- Hooks apply to ALL tools (use `if` statements to filter)
- Multiple plugins can register hooks without conflict
- Hooks run in registration order
- Hooks can be async
---
## Comparison with Other Event Hooks
### Available Tool Execution Hooks
| Hook | When | Use Case |
|------|------|----------|
| `tool.execute.before` | Before tool runs | Input validation, context injection, preprocessing |
| `tool.execute.after` | After tool completes | Output formatting, logging, analytics |
### Other Event Hooks (Not Used in Skills Plugin)
| Hook | When | Use Case |
|------|------|----------|
| `session.created` | Session starts | Welcome messages, initialization |
| `message.updated` | Message changes | Monitoring, logging |
| `session.idle` | Session completes | Cleanup, background tasks |
| `session.error` | Error occurs | Error handling, logging |
---
## Real-World Example: Skill Delivery Flow
### Step 1: Agent Calls Skill Tool
```
Agent: "Use the brand-guidelines skill"
OpenCode: Calls skills_brand_guidelines tool
```
### Step 2: Before Hook Fires
```typescript
const beforeHook = async (input, output) => {
// input.tool = "skills_brand_guidelines"
// input.sessionID = "ses_abc123"
// input.agent = "my-helper"
if (input.tool.startsWith("skills_")) {
const skill = skillMap.get("skills_brand_guidelines")
// skill = {
// name: "brand-guidelines",
// description: "Brand guidelines for the project",
// content: "# Brand Guidelines\n\n...",
// fullPath: "/path/to/skill"
// }
await ctx.client.session.prompt({
path: { id: "ses_abc123" },
body: {
agent: "my-helper",
noReply: true,
parts: [
{
type: "text",
text: "📚 Skill: brand-guidelines\nBase directory: /path/to/skill\n\n# Brand Guidelines\n\n..."
}
]
}
})
}
}
```
**Result:** Skill content added to conversation, no AI response
### Step 3: Tool Executes
```typescript
async execute(args, toolCtx) {
// Minimal logic
return `Skill activated: brand-guidelines`
}
```
**Result:** Simple confirmation returned
### Step 4: After Hook Fires
```typescript
const afterHook = async (input, output) => {
// input.tool = "skills_brand_guidelines"
// output.output = "Skill activated: brand-guidelines"
if (input.tool.startsWith("skills_")) {
const skill = skillMap.get("skills_brand_guidelines")
if (skill && output.output) {
output.title = `📚 brand-guidelines`
}
}
}
```
**Result:** Output title enhanced with emoji
### Step 5: Agent Receives Result
```
Conversation History:
├─ User: "Use the brand-guidelines skill"
├─ Tool Call: skills_brand_guidelines
├─ Silent Message: "📚 Skill: brand-guidelines\n..."
├─ Tool Result: "Skill activated: brand-guidelines"
│ (with title: "📚 brand-guidelines")
└─ Agent: "I now have the brand guidelines. I can help with..."
```
---
## Testing Hooks
### Testing Before Hook
```typescript
describe("beforeHook", () => {
it("should inject skill content for skill tools", async () => {
const input = {
tool: "skills_brand_guidelines",
sessionID: "ses_test",
agent: "test-agent"
}
const output = { args: {} }
const mockPrompt = jest.fn()
ctx.client.session.prompt = mockPrompt
await beforeHook(input, output)
expect(mockPrompt).toHaveBeenCalledWith(
expect.objectContaining({
path: { id: "ses_test" },
body: expect.objectContaining({
agent: "test-agent",
noReply: true,
parts: expect.arrayContaining([
expect.objectContaining({
type: "text",
text: expect.stringContaining("brand-guidelines")
})
])
})
})
)
})
it("should skip non-skill tools", async () => {
const input = { tool: "read_file", sessionID: "ses_test" }
const output = { args: {} }
const mockPrompt = jest.fn()
ctx.client.session.prompt = mockPrompt
await beforeHook(input, output)
expect(mockPrompt).not.toHaveBeenCalled()
})
})
```
### Testing After Hook
```typescript
describe("afterHook", () => {
it("should add emoji title for skill tools", async () => {
const input = { tool: "skills_brand_guidelines" }
const output = { output: "Skill activated" }
await afterHook(input, output)
expect(output.title).toBe("📚 brand-guidelines")
})
it("should skip non-skill tools", async () => {
const input = { tool: "read_file" }
const output = { output: "File content" }
await afterHook(input, output)
expect(output.title).toBeUndefined()
})
it("should skip if output is missing", async () => {
const input = { tool: "skills_brand_guidelines" }
const output = { output: null }
await afterHook(input, output)
expect(output.title).toBeUndefined()
})
})
```
---
## Common Patterns
### Pattern 1: Tool-Specific Hooks
```typescript
const beforeHook = async (input, output) => {
switch (input.tool) {
case "skills_brand_guidelines":
// Handle brand guidelines
break
case "skills_api_reference":
// Handle API reference
break
default:
// Skip non-skill tools
}
}
```
### Pattern 2: Conditional Processing
```typescript
const beforeHook = async (input, output) => {
if (input.tool.startsWith("skills_")) {
const skill = skillMap.get(input.tool)
if (skill && skill.allowedTools?.includes(input.agent)) {
// Process only if allowed
}
}
}
```
### Pattern 3: Logging & Monitoring
```typescript
const beforeHook = async (input, output) => {
if (input.tool.startsWith("skills_")) {
console.log(`[BEFORE] Skill tool called: ${input.tool}`)
console.log(`[BEFORE] Session: ${input.sessionID}`)
}
}
const afterHook = async (input, output) => {
if (input.tool.startsWith("skills_")) {
console.log(`[AFTER] Skill tool completed: ${input.tool}`)
console.log(`[AFTER] Output length: ${output.output?.length || 0}`)
}
}
```
### Pattern 4: Error Handling
```typescript
const beforeHook = async (input, output) => {
try {
if (input.tool.startsWith("skills_")) {
const skill = skillMap.get(input.tool)
if (!skill) {
throw new Error(`Skill not found: ${input.tool}`)
}
// Process skill
}
} catch (error) {
console.error(`Hook error:`, error)
// Don't rethrow - let tool execute anyway
}
}
```
---
## Key Takeaways
1. **Hooks are middleware**: They intercept tool execution at specific points
2. **Before hook**: For preprocessing, validation, context injection
3. **After hook**: For output enhancement, logging, analytics
4. **Lookup maps**: Enable O(1) access instead of O(n) search
5. **Separation of concerns**: Tools do one thing, hooks do another
6. **Composability**: Multiple plugins can register hooks without conflict
7. **Testability**: Each component can be tested independently
8. **Maintainability**: Changes are isolated to specific hooks
---
## References
- **OpenCode Events**: `context/capabilities/events.md`
- **Tool Definition**: `context/capabilities/tools.md`
- **Best Practices**: `context/reference/best-practices.md`
- **Skills Plugin Example**: `skills-plugin/example.ts`
- **Hook Lifecycle**: `skills-plugin/hook-lifecycle-and-patterns.md`
- **Implementation Pattern**: `skills-plugin/implementation-pattern.md`

View File

@@ -0,0 +1,53 @@
<!-- Context: openagents-repo/tools | Priority: low | Version: 1.0 | Updated: 2026-02-15 -->
# Building Custom Tools
Plugins can add custom tools that OpenCode agents can call autonomously.
## Tool Definition
Custom tools use Zod for schema definition and the `tool` helper from `@opencode-ai/plugin`.
```typescript
import { z } from 'zod';
import { tool } from '@opencode-ai/plugin';
export const MyCustomTool = tool(
z.object({
query: z.string().describe('Search query'),
limit: z.number().default(10).describe('Results limit')
}),
async (args, context) => {
const { query, limit } = args;
// Implementation logic
return { success: true, data: [] };
}
).describe('Search your database');
```
## Shell-based Tools
You can leverage Bun's shell API (`$`) to run commands in any language.
```typescript
export const PythonCalculatorTool = tool(
z.object({ expression: z.string() }),
async (args, context) => {
const { $ } = context;
const result = await $`python3 -c 'print(eval("${args.expression}"))'`;
return { result: result.stdout };
}
).describe('Calculate mathematical expressions');
```
## Integration
To register tools in your plugin:
```typescript
export const MyPlugin = async (context) => {
return {
tool: [MyCustomTool, PythonCalculatorTool]
};
};
```

View File

@@ -0,0 +1,36 @@
<!-- Context: openagents-repo/context-overview | Priority: low | Version: 1.0 | Updated: 2026-02-15 -->
# OpenCode Plugin Context Library
This library provides structured context for AI coding assistants to understand, build, and extend OpenCode plugins. Depending on your task, you can load specific parts of this library.
## 📚 Library Map
### 🏗️ Architecture
Foundational concepts of how plugins are registered and executed.
- [Overview](./architecture/overview.md): Basic structure, registration, and context object.
- [Lifecycle](./architecture/lifecycle.md): Packaging, manifest, and session lifecycle.
### 🛠️ Capabilities
Deep dives into specific plugin features.
- [Events](./capabilities/events.md): Detailed list of all 25+ hookable events.
- [Events: Skills Plugin](./capabilities/events_skills.md): Practical example of event hooks in the Skills Plugin.
- [Tools](./capabilities/tools.md): How to build and register custom tools using Zod.
- [Agents](./capabilities/agents.md): Creating and configuring custom AI agents.
### 📖 Reference
Guidelines and troubleshooting.
- [Best Practices](./reference/best-practices.md): Message injection workarounds, security, and performance.
### 🧩 Claude Code Plugins (External)
Claude Code plugin system documentation (harvested from external docs).
- [Concepts: Plugin Architecture](./concepts/plugin-architecture.md): Core concepts and structure
- [Guides: Creating Plugins](./guides/creating-plugins.md): Step-by-step creation
- [Guides: Migrating to Plugins](./guides/migrating-to-plugins.md): Convert standalone to plugin
- [Lookup: Plugin Structure](./lookup/plugin-structure.md): Directory reference
## 🚀 How to use this library
If you are asking an AI to build a new feature:
1. **For a new tool**: Provide `architecture/overview.md` and `capabilities/tools.md`.
2. **For reacting to events**: Provide `capabilities/events.md`.
3. **For overall plugin architecture**: Provide `architecture/overview.md` and `architecture/lifecycle.md`.

View File

@@ -0,0 +1,28 @@
<!-- Context: openagents-repo/best-practices | Priority: low | Version: 1.0 | Updated: 2026-02-15 -->
# Best Practices & Limitations
## Message Injection Workarounds
**The Reality**: The message system is largely read-only. You cannot mutate messages mid-stream or inject text directly into an existing message part.
### What Doesn't Work
- Modifying `event.data.content` in `message.updated`.
- Retroactively changing AI responses.
### What Works
1. **Initial Context**: Use `session.created` to inject a starting message using `client.session.prompt()`.
2. **Prompt Decoration**: Use `client.tui.appendPrompt()` to add text to the user's input box before they hit enter.
3. **Tool Interception**: Use `tool.execute.before` to modify arguments *before* the tool runs.
4. **On-Demand Context**: Provide custom tools that the AI can call when it needs more information.
## Security
- Always validate tool inputs in `tool.execute.before`.
- Use environment variables for sensitive data; do not hardcode API keys.
- Be careful with the `$` shell API to prevent command injection.
## Performance
- Avoid heavy synchronous operations in event handlers as they can block the TUI.
- Use the `session.idle` event for cleanup or background sync tasks.