docs(processes): process spawn policy, hardening and redaction

extensions.md: api.Processes and api.Redactor, ExecAsync on the policy,
ProcessSpawnEvent, ctx.TimeZone and PrepareProcessAsync, and a Spawning
processes section. architecture.md: Processes slice, the Abstractions
contract, a Processes and secrets section, and where the loop redacts.
CLAUDE.md: Processes slice and bullet.

Spec: status implemented (scheduler and MCP spawners come with their specs);
a hook returning null fails the spawn; test projects consume the one-shot
hardening from a module initializer; the Hardener seam; 'allow: none' in the
Telegram log line.
This commit is contained in:
EugeneTes
2026-09-26 19:23:09 +02:00
parent 694ef3594e
commit 5f33d139b3
4 changed files with 106 additions and 17 deletions

View File

@@ -12,8 +12,10 @@ tests/* xunit
`Abstractions` is deliberately small and stable: messages and content blocks, `Model` and `IProvider`,
the streaming event protocol, `ITool`, the extension API and its events, session entries and stores,
`IUserInterface`, and the `AgentSessionEvent` family a UI subscribes to. Extensions reference only this
assembly, which is what allows them to be loaded from separate directories.
`IUserInterface`, the `AgentSessionEvent` family a UI subscribes to, and the process contract in `Processes/`
(`IProcessSpawnPolicy`, `ProcessSpawnRequest`/`ProcessSpawnContext`, `ProcessSpawnHook`, `ProcessSpawnEvent`,
`ISecretRedactor`). Extensions reference only this assembly, which is what allows them to be loaded from separate
directories.
## Vertical slices
@@ -30,6 +32,7 @@ owns everything for that feature and exposes one entry type:
| `SystemPrompt` | `SystemPromptBuilder` | Base prompt, tools section, guidelines, project context files, environment |
| `Compaction` | `Compactor` | When to compact, what to summarise, the summary prompt |
| `Agent` | `AgentSession`, `AgentSessionFactory` | The conversation facade and the loop |
| `Processes` | `ProcessSpawnPolicy` | The environment, command and cwd of every agent-requested process (deny-list, allow-list, secret values, host hooks), host hardening, secret redaction |
Slices talk to each other only through their entry types and the Abstractions contracts. `AgentSession`
is the composition root of one conversation; a host never touches the other slices directly.
@@ -70,6 +73,8 @@ loop:
context → extensions may rewrite the message list
stream the provider → message_start / message_update* / message_end (with retry + overflow recovery)
tool calls? → tool_call hook, execute (parallel by default), tool_result hook, tool result messages
(every result, progress update and error is redacted before tool_result handlers,
and again after them, before tool_execution_end and the persisted message)
turn_end
more tool calls or steering messages or continue requested? → next turn
follow-up queued? → next turn
@@ -101,6 +106,34 @@ Two independent event systems exist:
(`Block`, `Cancel`, `MarkHandled`) or setting properties (`Text`, `Payload`, `Message`, `Override`).
Handlers run sequentially in load order and see each other's changes; some events short-circuit.
## Processes and secrets
Every process started on the agent's behalf (`bash`, `IExtensionApi.ExecAsync`, and, with their specs, scheduler
scripts and MCP servers) gets its command, cwd and environment from one `IProcessSpawnPolicy`, configured through
`AgenticCoreOptions.ProcessEnvironment`:
```
inherit the host environment → filter (Deny, Allow, SecretValues) → explicit request.Environment (never filtered)
→ TZ from TimeZoneId → host hooks → ProcessSpawnEvent to extensions (only in a session, via PrepareProcessAsync)
```
The result is the child's complete environment; spawners clear `ProcessStartInfo.Environment` and copy it in. A
throwing hook or handler fails the spawn instead of falling back to the inherited environment.
`HostProcessHardening` makes the host non-dumpable on Linux (`prctl(PR_SET_DUMPABLE, 0)`), so a same-user child
can't read `/proc/<host pid>/environ` or `ptrace` the host. It runs from the `ProcessSpawnPolicy` constructor, or
explicitly from a host at startup (the Telegram host does), and only once. It warns when `DOTNET_EnableDiagnostics=0`
is missing, since the diagnostic socket would hand out the same environment. `HardenHostProcess = false` turns it off
for debugging. Test projects make the one call with the flag off from a module initializer, so the test runner stays
dumpable.
`ISecretRedactor` replaces every `SecretValues` entry with `[REDACTED]`. `AgentSession` applies it to tool
results, progress updates and error messages (see the loop above), so every tool is covered in one place.
The Claude Code provider's own `claude` processes are outside the policy on purpose: they run the model and already
get an allow-listed environment. None of this is a sandbox: the agent still runs as the host's OS user and can read
the files that user can read.
## Persistence
A session is a tree of entries (`message`, `model_change`, `thinking_level_change`, `compaction`, `custom`,