- name
- add-backend
- description
- Guide for adding a backend (Rust or Python) to the agent-sec-core security middleware. Use when creating new backends, integrating Rust or Python code into the security middleware, or extending with new backend actions.
- arguments
- [{"name":"backend_name","description":"Name of the new backend (e.g. 'code_verify'). Spaces are converted to underscores for code identifiers.","required":true},{"name":"backend_type","description":"Backend implementation type: 'rust' or 'python'","required":true},{"name":"module_path","description":"For python type: module path (e.g. 'agent_sec_cli.code_verify.verifier'). Required when backend_type=python.","required":false}]
# Adding a Backend to Security Middleware
This skill walks through the **complete, end-to-end** process of adding a backend
(Rust or Python) to the security middleware, wiring it into the router, and
exposing it through the CLI.
> **Unified interface**: Both Rust and Python backends implement the same
> `execute(ctx, **kwargs) → ActionResult` contract. The middleware doesn't care
> about the implementation language.
## Backend Type Selection
| Type | Use Case | Pros | Cons |
|------|----------|------|------|
| **rust** | Performance-critical, CPU-intensive tasks | High performance, memory safety | Requires Rust toolchain, compilation |
| **python** | Rapid development, glue code, existing libraries | Fast iteration, rich ecosystem | Slower execution, GIL limitations |
## Naming Convention
Derive all identifiers from the `backend_name` argument:
| Concept | Rule | Example (`backend_name` = "code verify") |
|---------|------|------------------------------------------|
| action_name | lowercase, underscores | `code_verify` |
| Backend class | PascalCase + `Backend` | `CodeVerifyBackend` |
| Python module | `{action_name}.py` | `code_verify.py` |
| lifecycle category | same as action_name | `code_verify` |
**Rust-specific** (only when `backend_type=rust`):
| Concept | Rule | Example |
|---------|------|----------|
| Rust function | same as action_name | `code_verify` |
| Request struct | PascalCase + `Request` | `CodeVerifyRequest` |
| Response struct | PascalCase + `Response` | `CodeVerifyResponse` |
---
## 1. Architecture Overview
Both backend types follow the same execution flow:
```
agent-sec-cli ──→ security_middleware.invoke("{action_name}", **kwargs)
│
├─ router.get_backend("{action_name}")
│ └─ _REGISTRY["{action_name}"] → "security_middleware.backends.{action_name}"
│ └─ lazy import → {ActionName}Backend()
│
├─ backend.execute(ctx, **kwargs) → ActionResult
│ │
│ ├─ [Rust] from agent_sec_cli._native import {action_name}
│ │ {action_name}(json_in) → json_out
│ │
│ └─ [Python] import {module_path}
│ module.function(**kwargs) → result
│
└─ lifecycle.post_action() → SecurityEvent → JSONL
```
**Key contract**: Every backend is a Python class with an `execute(ctx, **kwargs) → ActionResult`
method. The implementation language (Rust/Python) is an **implementation detail** — the middleware
never calls Rust or module functions directly.
---
## 2. Create the Python Backend Wrapper
The Python backend wrapper is the **unified interface** that the middleware calls. It delegates
to either Rust or Python implementation based on `backend_type`.
### 2.1 Choose Template
- **For `backend_type=rust`**: Use `templates/rust_backend.py`
- **For `backend_type=python`**: Use `templates/python_backend.py`
### 2.2 Create Backend File
Create `agent-sec-cli/src/agent_sec_cli/security_middleware/backends/{action_name}.py`
Copy the appropriate template and replace placeholders:
- `{backend_name}` → actual backend name (e.g., "code_verify")
- `{BackendName}` → PascalCase class name (e.g., "CodeVerify")
- `{action_name}` → action name for Rust calls (e.g., "code_verify")
- `{module_path}` → Python module path (only for python type, e.g., "agent_sec_cli.code_verify.verifier")
**Convention**: Class name = PascalCase of module name + `Backend`.
> **IMPORTANT — `stdout` / `error` contract**: The CLI (`agent-sec-cli`) only
> prints `result.stdout` and `result.error`. If a backend returns an `ActionResult`
> with both `stdout` and `error` empty, the CLI produces **no output at all**.
> Every `ActionResult` **must** populate at least one of:
>
> | Field | When to set |
> |-------|-------------|
> | `stdout` | Always on success — human-readable text for the terminal |
> | `error` | Always on failure — written to stderr by the CLI |
>
> A helper like `_format_stdout()` keeps formatting in one place and makes it
> easy to test independently.
---
## 3. Register Backend in Router and Lifecycle
### 3.1 Register in Router
Edit `agent-sec-cli/src/agent_sec_cli/security_middleware/router.py` — add to `_REGISTRY`:
```python
_REGISTRY: Dict[str, str] = {
# ... existing entries ...
"{action_name}": "agent_sec_cli.security_middleware.backends.{action_name}",
}
```
### 3.2 Add Lifecycle Category Mapping
Edit `agent-sec-cli/src/agent_sec_cli/security_middleware/lifecycle.py` — add to `_ACTION_CATEGORY`:
```python
_ACTION_CATEGORY: Dict[str, str] = {
# ... existing entries ...
"{action_name}": "{action_name}",
}
```
### 3.3 Add CLI Entry Point
Edit `src/agent_sec_cli/cli.py` — add a new `@app.command()` function:
```python
# {action_name} subcommand
@app.command()
def {action_name}(
param1: str = typer.Option("", "--param1", help="Parameter 1"),
# Add more arguments as needed for the backend
):
"""{ActionName} description."""
result = invoke("{action_name}", param1=param1)
if result.stdout:
typer.echo(result.stdout)
if result.error:
typer.echo(result.error, err=True)
raise typer.Exit(code=result.exit_code)
```
Now callable as:
```bash
agent-sec-cli {action_name} --param1 value
```
---
## 4. Rust-Specific Steps (backend_type=rust)
> Skip this section if `backend_type=python`.
> **Sub-skill available**: For complex Rust modules (multiple files, shared types,
> or workspace-scale projects), use the **`add-rust-module`** sub-skill at
> `sub-skills/add-rust-module.md`. It covers single-file modules, directory modules,
> and Cargo workspace layouts.
>
> The steps below cover the **simple inline case** (adding a function directly to
> `src/lib.rs`). For anything beyond a single function, delegate to the sub-skill:
>
> ```
> add-rust-module module_name="{action_name}" functions="{action_name}"
> ```
### 4.1 Add Rust Function to lib.rs
Edit `agent-sec-cli/src/lib.rs` — add your Rust function above the `#[pymodule]` block:
```rust
// ---------------------------------------------------------------------------
// {action_name}
// ---------------------------------------------------------------------------
#[derive(Deserialize)]
struct {ActionName}Request {
// Add domain-specific fields here
}
#[derive(Serialize)]
struct {ActionName}Response {
// Add domain-specific output fields here
}
/// Pure Rust logic — no Python API calls.
fn do_{action_name}(req: &{ActionName}Request) -> Result<{ActionName}Response, String> {
// Implement domain logic here
todo!("implement {action_name} logic")
}
#[pyfunction]
fn {action_name}(py: Python<'_>, request_json: &str) -> PyResult<String> {
let req: {ActionName}Request = serde_json::from_str(request_json)
.map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(
format!("Invalid JSON: {e}")
))?;
py.allow_threads(|| {
let resp = do_{action_name}(&req)
.map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e))?;
serde_json::to_string(&resp)
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(
format!("Serialization failed: {e}")
))
})
}
```
### 4.2 Register in #[pymodule]
Add this line inside the `_native` pymodule function in `src/lib.rs`:
```rust
m.add_function(wrap_pyfunction!({action_name}, m)?)?;
```
### 4.3 Update Python Backend Wrapper
Edit `agent-sec-cli/src/agent_sec_cli/security_middleware/backends/{action_name}.py`:
```python
from agent_sec_cli._native import {action_name} as rust_{action_name}
class {BackendName}Backend:
def execute(self, ctx, **kwargs) -> ActionResult:
try:
req = json.dumps(kwargs)
resp_json = rust_{action_name}(req)
resp = json.loads(resp_json)
return ActionResult(
success=True,
data=resp,
stdout=self._format_stdout(resp),
)
except Exception as exc:
return ActionResult(success=False, error=f"Rust error: {exc}", exit_code=1)
```
**Key changes**:
- No `RUST_AVAILABLE` check needed (Rust code is always available)
- Import directly from `agent_sec_cli._native`
- No Python fallback (unless you intentionally keep it)
### 4.4 Build and Test
#### 4.4.1 Rebuild with maturin
```bash
cd agent-sec-cli
uv run maturin develop --release
```
#### 4.4.2 Test from Python
```python
from agent_sec_cli._native import {action_name}
import json
req = json.dumps({"param": "value"})
resp = {action_name}(req)
print(json.loads(resp))
```
#### 4.4.3 Run Rust Tests
```bash
cd agent-sec-cli
cargo test
```
### 4.5 Add Dependencies (if needed)
If your Rust function needs additional crates (e.g., `serde`, `serde_json`),
edit `agent-sec-cli/Cargo.toml`:
```toml
[dependencies]
pyo3 = { version = "0.20", features = ["extension-module"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
```
### 4.6 Complex Rust Modules
If the Rust logic is too large for a single function in `lib.rs`, use the
**`add-rust-module`** sub-skill to create a dedicated module:
```
add-rust-module module_name="{action_name}" functions="{action_name}" complex="true"
```
This will:
1. Create `src/{action_name}/mod.rs` with proper sub-module layout
2. Wire `mod {action_name};` into `src/lib.rs`
GitHub에서 보기