242 lines
9.5 KiB
Markdown
242 lines
9.5 KiB
Markdown
# RunContext — Session Permissions & Configuration
|
|
|
|
**Single source of truth for RunContext.** Consolidates resolution, fields, usage, and API.
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
Each session can have an active **RunContext** that controls:
|
|
|
|
- **Approval policy** — which permission group (`security_group`) applies to tool calls
|
|
- **System prompt injection** — dynamic prompt fragments per session
|
|
- **File-write pre-authorization** — paths auto-approved for writes (`allow_fs_writes`)
|
|
- **File-read auto-allow** — paths auto-approved for reads (working dir + `docs/` + `skills/` + `allow_fs_reads` + anything writable)
|
|
- **Working directory** — effective CWD for tool calls and file operations
|
|
|
|
`RunContext` is a JSON blob stored in the DB:
|
|
- `chat_sessions.run_context` — interactive web/mobile sessions
|
|
- `scheduled_jobs.run_context` — cron tasks
|
|
- `projects.run_context` — project-level defaults
|
|
- `project_tickets.run_context` — ticket-level overrides
|
|
|
|
---
|
|
|
|
## Fields
|
|
|
|
| Field | Type | Default | Purpose |
|
|
|-------|------|---------|---------|
|
|
| `security_group` | `Option<String>` | `null` | Permission group ID for approval rule lookup. Rules in this group take precedence over `"default"`. |
|
|
| `system_prompt` | `Vec<String>` | `[]` | Prompt fragments injected as dynamic system context every turn (joined with `"\n\n"`). |
|
|
| `allow_fs_writes` | `Vec<String>` | `[]` | Paths pre-authorized for file writes. Resolved against the working dir; recursive directory prefix match (no globs). Entries are also readable (write implies read). |
|
|
| `allow_fs_reads` | `Vec<String>` | `[]` | Extra **read-only** grants, beyond the working dir / `docs/` / `skills/` (always-safe baseline) and `allow_fs_writes`. Same prefix-match semantics. |
|
|
| `working_directory` | `Option<String>` | `null` | Effective WD for tool calls; `null` = Skald's process cwd. |
|
|
|
|
---
|
|
|
|
## Applicative Methods
|
|
|
|
`RunContext` exposes these methods (the session handler is agnostic to internal fields):
|
|
|
|
```rust
|
|
rc.tool_group_id() -> Option<&str> // for approval rule lookup
|
|
rc.extra_system_prompt() -> Option<String> // joins system_prompt with "\n\n"
|
|
rc.effective_working_dir() -> PathBuf // configured path or process cwd
|
|
rc.is_write_allowed(path) -> bool // pre-auth check for file writes
|
|
rc.is_read_allowed(path) -> bool // pre-auth check for file reads
|
|
```
|
|
|
|
Both `is_*_allowed` canonicalize the path first (resolving `..` and symlinks via
|
|
`tools::fs::canonicalize_for_policy`) so traversal/symlink escapes cannot widen a grant.
|
|
|
|
---
|
|
|
|
## Resolution at Session Creation
|
|
|
|
**Order of precedence** (`ChatSessionManager::create_session` or `ChatHub::provision_session`):
|
|
|
|
1. **Explicit `run_context` parameter** — JSON blob passed at session creation. Persisted in DB immediately so the handler reads it at construction.
|
|
2. **Config-driven defaults** — from `config.yml` (per-source or per-agent), or TIC's `tic.run_context` key.
|
|
3. **None** — all RunContext methods return zero values (`tool_group_id()` → `None`, `is_write_allowed()` → `false`, `effective_working_dir()` → process cwd).
|
|
|
|
The `RunContext` is stored in `ChatSessionHandler::run_context` (`RwLock<Option<RunContext>>`). The handler **reads it once at construction** and **never directly accesses its internal fields** — only calls applicative methods.
|
|
|
|
### Session Handler Usage
|
|
|
|
| Method | Used for |
|
|
|--------|----------|
|
|
| `tool_group_id()` | Approval rule lookup (passed to `ApprovalManager::check()`) |
|
|
| `extra_system_prompt()` | Injected as dynamic system tail in `build_agent_config` (see [llm-loop.md](llm-loop.md)) |
|
|
| `is_write_allowed(path)` | Fast-path for file write tools; upgrades a `Require` decision to `Allow` |
|
|
| `is_read_allowed(path)` | Fast-path for file read tools; upgrades a `Require` decision to `Allow` |
|
|
| `effective_working_dir()` | WD injection for file tools and `execute_cmd` |
|
|
|
|
---
|
|
|
|
## Runtime Update
|
|
|
|
**Endpoint:** `POST /api/sessions/{id}/run_context`
|
|
|
|
**Body:** Full `RunContext` JSON (or `null` to clear):
|
|
|
|
```json
|
|
{
|
|
"security_group": "cron_restrictive",
|
|
"system_prompt": ["Always reply in English.", "Use metric units."],
|
|
"allow_fs_writes": ["data/output", "logs/*"],
|
|
"working_directory": "/projects/skald"
|
|
}
|
|
```
|
|
|
|
**Effects:**
|
|
- Updates `chat_sessions.run_context` in DB
|
|
- If the handler is live in memory, calls `handler.set_run_context()` immediately (no restart needed)
|
|
- Changes take effect on the next turn
|
|
|
|
---
|
|
|
|
## Approval Gate Integration
|
|
|
|
Rules are scoped to **permission groups** (`tool_permission_groups` table). A session's `RunContext.security_group` field references a group; rules in that group take precedence over `"default"`.
|
|
|
|
### Evaluation Chain
|
|
|
|
```
|
|
chat_session.run_context (JSON blob)
|
|
└─► RunContext.tool_group_id() → e.g. "cron_restrictive"
|
|
│
|
|
├─ rules WHERE group_id = "cron_restrictive" ← evaluated first
|
|
└─ rules WHERE group_id = "default" ← fallback
|
|
```
|
|
|
|
**Default behavior:** If a session has no `run_context` or the blob has no `security_group`, only `"default"` group rules apply.
|
|
|
|
The `"default"` group is seeded automatically at startup and **cannot be deleted**. Its rules can be freely edited.
|
|
|
|
See [approval/index.md](../approval/index.md) for rule evaluation and pattern matching.
|
|
|
|
---
|
|
|
|
## Filesystem fast-paths and gate precedence
|
|
|
|
`RunContext` pre-authorizes filesystem access **without a human approval prompt**, but it
|
|
**never overrides an explicit `Deny`**. The gate in `llm_loop.rs` evaluates in this order:
|
|
|
|
```
|
|
1. ApprovalManager::check() → Allow | Deny | Require
|
|
2. if Require and is_file_read_tool → rc.is_read_allowed(path) ? Allow : Require
|
|
if Require and is_file_write_tool → rc.is_write_allowed(path) ? Allow : Require
|
|
```
|
|
|
|
So a `Deny` (e.g. the seeded `secrets/` rule) always wins, and the fast-path only relaxes
|
|
a `Require` to `Allow` — the same semantics as a session bypass. When the session has no
|
|
`RunContext`, a default one is used (working dir = process cwd), so `docs/`/`skills/` and the
|
|
cwd are still auto-readable.
|
|
|
|
### Writes — `allow_fs_writes`
|
|
|
|
Pre-authorizes writes to the listed paths. Resolved against the working dir; matching is a
|
|
recursive directory prefix (canonicalized — no globs). `memory/` is always writable via a
|
|
separate hardcoded exception in the approval engine.
|
|
|
|
**Common presets:** `["data"]` (project output), `["logs", "tmp"]` (temporary files).
|
|
|
|
### Reads — auto-allow roots
|
|
|
|
Reads are auto-allowed (no prompt) for, in order:
|
|
|
|
1. the **working directory** itself,
|
|
2. its **`docs/`** and **`skills/`** subtrees (always-safe baseline),
|
|
3. every **`allow_fs_reads`** entry (read-only grants),
|
|
4. everything in **`allow_fs_writes`** (write implies read).
|
|
|
|
Anything else falls back to the approval rules of the `security_group`. Under a
|
|
`require`-default group this means reads outside these roots prompt for approval.
|
|
The built-in **`default`** group is itself require-by-default (its final catch-all is
|
|
`require *`; see [approval/index.md](../approval/index.md)), so these auto-allow roots are
|
|
load-bearing even for the default group — not just for explicitly-restrictive custom groups.
|
|
|
|
> **`secrets/` is denied.** The approval engine seeds `deny` rules for the read tools on
|
|
> `secrets` / `secrets/*` (see [approval/index.md](../approval/index.md)). Because `Deny`
|
|
> is evaluated first and is non-bypassable, `secrets/` stays unreadable even though it sits
|
|
> inside the auto-read working dir. The recursive read tools (`grep_files`, `list_files`)
|
|
> additionally skip any `secrets` directory during traversal, so a search rooted higher up
|
|
> cannot leak secret values.
|
|
|
|
---
|
|
|
|
## Project Integration
|
|
|
|
Projects can set default `RunContext` for all interactive and ticket chats under that project:
|
|
|
|
- **Project-level:** `POST /api/projects/{id}/run_context` → stored in `projects.run_context`
|
|
- **Ticket override:** `POST /api/projects/{project_id}/tickets/{ticket_id}/run_context` → stored in `project_tickets.run_context`
|
|
|
|
Ticket override **takes precedence** over project-level when a ticket chat is opened.
|
|
|
|
See [projects.md](../projects.md) for project lifecycle and `build_runtime_run_context`.
|
|
|
|
---
|
|
|
|
## Example Scenarios
|
|
|
|
### Scenario 1: Cron job with restricted permissions
|
|
|
|
**Config:**
|
|
```json
|
|
{
|
|
"security_group": "cron_restrictive",
|
|
"allow_fs_writes": ["logs/*"],
|
|
"working_directory": "/tmp"
|
|
}
|
|
```
|
|
|
|
**Effect:**
|
|
- All tool calls evaluated against `cron_restrictive` group rules (typically more restrictive)
|
|
- File writes to `logs/*` bypass approval entirely
|
|
- File operations use `/tmp` as working directory
|
|
|
|
### Scenario 2: Project ticket with context injection
|
|
|
|
**Config:**
|
|
```json
|
|
{
|
|
"system_prompt": ["You are fixing ticket #42: DB migration failure. Current logs are in `logs/migration.log`."],
|
|
"working_directory": "/projects/skald",
|
|
"allow_fs_writes": ["data/migrations/", "logs/*"]
|
|
}
|
|
```
|
|
|
|
**Effect:**
|
|
- Extra context prepended to system prompt every turn
|
|
- Ticket-scoped working directory for `execute_cmd`
|
|
- Migration files and logs can be written without approval
|
|
|
|
### Scenario 3: Interactive session (default)
|
|
|
|
**Config:**
|
|
```json
|
|
{
|
|
"security_group": null,
|
|
"system_prompt": [],
|
|
"allow_fs_writes": [],
|
|
"working_directory": null
|
|
}
|
|
```
|
|
|
|
**Effect:**
|
|
- Default approval group rules apply
|
|
- No extra system prompt
|
|
- All file writes require approval (if rule triggers)
|
|
- Reads of the cwd, `docs/` and `skills/` are auto-allowed; `secrets/` is denied; other reads follow the default group rules
|
|
- Uses Skald's process CWD
|
|
|
|
---
|
|
|
|
## When to Update This File
|
|
|
|
- Adding a new field to `RunContext`
|
|
- Changing resolution order or approval gate behavior
|
|
- Adding new pre-authorization scenarios
|
|
- Documenting config best practices
|