# Host Backends And Projections

This page documents the host-backend and projection surface used by `pydantic-acp`.

If you are integrating `langchain-acp`, read [LangChain ACP Projections and Event Projection Maps](langchain-acp/projections.md).

ACP Kit includes two small but important host-facing surfaces:

1. **session-scoped host backends**
2. **projection maps**

The first lets tools talk to the bound ACP client cleanly.
The second makes ACP updates look better in the UI.

## ClientFilesystemBackend

`ClientFilesystemBackend` is a thin adapter over ACP file APIs that automatically carries the active session id.

```python
from pydantic_acp import ClientFilesystemBackend

backend = ClientFilesystemBackend(client=client, session=session)
response = await backend.read_text_file("notes/todo.txt")
print(response.content)
```

Supported methods:

- `read_text_file(...)`
- `write_text_file(...)`

## ClientTerminalBackend

`ClientTerminalBackend` does the same for ACP terminal operations:

```python
from pydantic_acp import ClientTerminalBackend

backend = ClientTerminalBackend(client=client, session=session)
terminal = await backend.create_terminal("python", args=["-V"])
await backend.wait_for_terminal_exit(terminal.terminal_id)
output = await backend.terminal_output(terminal.terminal_id)
print(output.output)
```

Supported methods:

- `create_terminal(...)`
- `terminal_output(...)`
- `release_terminal(...)`
- `wait_for_terminal_exit(...)`
- `kill_terminal(...)`

## ClientHostContext

`ClientHostContext` groups both backends into one session-scoped object:

```python
from pydantic_acp import ClientHostContext

host = ClientHostContext.from_session(client=client, session=session)
file_response = await host.filesystem.read_text_file("notes/workspace.md")
terminal = await host.terminal.create_terminal("python", args=["-V"])
```

This is the most ergonomic option inside a session-aware factory or `AgentSource`.

### HostAccessPolicy

`HostAccessPolicy` adds a typed guardrail surface for host-backed file and terminal access.

```python
from pydantic_acp import ClientHostContext, HostAccessPolicy

host = ClientHostContext.from_session(
    client=client,
    session=session,
    access_policy=HostAccessPolicy(),
    workspace_root=session.cwd,
)
```

### What Problem It Solves

Host-backed integrations usually end up re-implementing the same decisions:

- should absolute paths be allowed
- should paths outside the active session cwd only warn or hard fail
- should workspace-root escapes be blocked
- should command cwd and command path arguments be treated with the same guardrail model

When that logic lives only in one downstream client, ACP-visible warnings and real execution policy drift apart. `HostAccessPolicy` gives ACP Kit one typed surface for both evaluation and enforcement.

Default policy behavior is conservative:

- absolute file paths warn
- paths outside the active session cwd warn
- paths outside the configured workspace root deny
- command cwd escapes and command path targets are evaluated with the same model

When the policy returns `deny`, the client backend raises `PermissionError` before the ACP request is sent.

### Policy Shape

`HostAccessPolicy` currently controls seven decision points:

- `absolute_path`
- `path_outside_cwd`
- `path_outside_workspace`
- `command_cwd_outside_cwd`
- `command_cwd_outside_workspace`
- `command_external_paths`
- `command_paths_outside_workspace`

Each decision point resolves to:

- `allow`
- `warn`
- `deny`

You can also start from named presets:

```python
from pydantic_acp import HostAccessPolicy

strict_policy = HostAccessPolicy.strict()
permissive_policy = HostAccessPolicy.permissive()
```

Use `strict()` when a coding agent should stay tightly inside the declared workspace. Use `permissive()` when the host still wants visibility into risk but does not want ACP Kit to deny as aggressively.

The evaluation objects are intentionally UI-friendly:

```python
evaluation = strict_policy.evaluate_command(
    'python',
    args=['../scripts/build.py'],
    session_cwd=session.cwd,
    workspace_root=session.cwd,
)

print(evaluation.headline)
print(evaluation.message)
print(evaluation.recommendation)
```

This makes it easier for ACP clients and downstream integrations to render one consistent warning surface without rebuilding policy text manually.

Minimal verified path example:

```python
from pathlib import Path

from pydantic_acp import HostAccessPolicy

policy = HostAccessPolicy.strict()
evaluation = policy.evaluate_path(
    '../notes.txt',
    session_cwd=Path('/workspace/app'),
    workspace_root=Path('/workspace/app'),
)

assert evaluation.disposition == 'deny'
assert evaluation.should_deny
assert 'outside_cwd' in evaluation.risk_codes
```

### Evaluation Surfaces

Path evaluation returns `HostPathEvaluation`. Command evaluation returns `HostCommandEvaluation`.

Both surfaces expose:

- `disposition`
- `message`
- `headline`
- `recommendation`
- `risks`
- `risk_codes`
- `primary_risk`
- `has_risks`
- `should_warn`
- `should_deny`
- `summary_lines()`

This split is deliberate:

- `evaluate_*` is for UI, previews, approval cards, or dry-run decisions
- `enforce_*` is for actual blocking behavior before ACP host requests are sent

### File And Command Evaluation Model

File access is evaluated against:

- the active session cwd
- the configured workspace root, if provided
- whether the original input path was absolute

Command access is evaluated against:

- the resolved command cwd
- the configured workspace root, if provided
- path-like command arguments such as `../file.py`, `/tmp/outside.txt`, or `--output=../dist/result.txt`

The current command-path detection is intentionally heuristic. It is designed to catch obvious path targets and drive better guardrails or UI warnings, not to be a full shell parser.

### Recommended Integration Pattern

Use the same policy in two places:

1. host backend enforcement
2. client-side projection or approval UX

That way:

- the warning a user sees
- and the rule that actually blocks execution

come from the same evaluation model.

Example:

```python
policy = HostAccessPolicy.strict()

host = ClientHostContext.from_session(
    client=client,
    session=session,
    access_policy=policy,
    workspace_root=session.cwd,
)

evaluation = policy.evaluate_command(
    'python',
    args=['../scripts/build.py'],
    session_cwd=session.cwd,
    workspace_root=session.cwd,
)
```

### Current Scope And Limits

`HostAccessPolicy` is intentionally narrow today.

It does:

- evaluate file paths
- evaluate command cwd and obvious path-like arguments
- return typed risk information
- enforce `deny` before ACP file or terminal requests are sent

It does not yet:

- rewrite or sanitize commands
- parse full shell syntax
- automatically wire itself through every runtime seam
- replace product-level approval UX

The current value is consistency: integrations can stop rebuilding one-off guardrail logic and use one native ACP Kit surface instead.

## Projection Maps

Projection maps do not change tool execution. They change how ACP renders the resulting updates.

### FileSystemProjectionMap

Use this for tool families that correspond to file reads, file writes, or shell commands:

```python
from pydantic_acp import FileSystemProjectionMap

projection = FileSystemProjectionMap(
    read_tool_names=frozenset({"mcp_repo_read_file", "mcp_host_read_workspace_file"}),
    write_tool_names=frozenset({"mcp_host_write_workspace_file"}),
    bash_tool_names=frozenset({"mcp_host_run_command"}),
)
```

This lets ACP clients render:

- read tools as diff-like previews
- write tools as file diffs
- shell tools as command previews or terminal references

### Composing Projection Maps

Multiple projection maps can be combined:

```python
from pydantic_acp import compose_projection_maps

projection_map = compose_projection_maps(filesystem_projection, hook_projection)
```

In practice, most setups pass them through `AdapterConfig.projection_maps`.

## When To Use Host Backends

Use host backends when the ACP client should remain the authority for filesystem or shell access.

That is the right design for:

- editor integrations
- workspace-local coding agents
- security-reviewed command execution flows
- clients that want full visibility into shell creation and release
