fix(tools): lower working directory and workspace into fs tool descriptions

The process cwd was nowhere in the model's context: shells start in the
inherited process cwd (spawn_group_leader passes no cwd), relative file
paths resolve against it, but nothing told the model where it was
standing — in stock Docker every shell ran in /data while user files sat
in the /workspace mount, and the model's only recourse was to probe with
pwd (#857, #833).

Lower both facts into the tool schemas, where they gate intrinsically on
tool availability (a persona without fs tools carries no note, and
coordinator envelopes are untouched):

- tools/*.json: cwd_note/workspace_note metadata templates on bash,
  read_file, write_file, edit_file, search, diff_file; bash also states
  the fresh-shell-per-call semantics (cd does not persist) and drops a
  stale reference to the removed man tool.
- tools.apply_cwd_context(): renders the notes into descriptions;
  deep-copies noted tools (the fs dicts are shared across
  TOOLS/INTERACTIVE_TOOLS/TASK_AGENT_TOOLS and aliased through
  merge_mcp_tools), passes note-less tools through by reference.
- ChatSession._apply_cwd_notes(): wraps every fresh interactive build of
  _tools AND _task_tools (construction, MCP catalog change, MCP
  disconnect) — assignment-time, so the wire tools block stays
  byte-stable for provider prompt caches. os.getcwd() is OSError-guarded
  (MCP rebuilds run on a background thread; eval tears down its
  workdir); the workspace hint drops when the dir is missing or equals
  the cwd. Task-agent sub-agents carry their own notes via _task_tools,
  independent of parent persona visibility.
- config.get_workspace_dir(): [tools] workspace_dir with
  TURNSTONE_WORKSPACE env fallback (searxng pattern), informational
  only — no chdir, no path confinement (per-workstream working-dir
  grants are a separate planned feature).
- Dockerfile: ENV TURNSTONE_WORKSPACE=/workspace so stock deployments
  surface the mount with zero operator config.
- docs/docker.md: document the /data working directory, the
  working_dir: /workspace compose override as the operator-level fix,
  and the SQLite-fallback-DB-in-cwd caveat.

Closes #857
This commit is contained in:
Patrick Buckley
2026-07-19 03:49:13 -07:00
parent 83277a4d17
commit 460308241d
15 changed files with 465 additions and 21 deletions
+30
View File
@@ -252,6 +252,7 @@ interface, or anyone who can reach it can search through your instance.
| Variable | Default | Description |
|----------|---------|-------------|
| `WORKSPACE_MOUNT` | empty volume | Host directory bind-mounted at `/workspace` for the model to read/write |
| `TURNSTONE_WORKSPACE` | `/workspace` (image env) | Directory named as the user's workspace in the model's tool descriptions; informational only — see [Working directory](#working-directory) |
| `SKIP_PERMISSIONS` | — | Set to any value to auto-approve all tool calls (dev only) |
| `MCP_CONFIG` | — | Path to an MCP server config file |
| `TURNSTONE_IMAGE_TAG` | `latest` | ghcr.io image tag — production stack |
@@ -276,6 +277,35 @@ docker compose build --no-cache # rebuild from scratch
| `workspace` | `/workspace` (unless `WORKSPACE_MOUNT` is set) |
| `caddy-data` / `caddy-config` | Caddy's local CA and config (dev stack) |
## Working directory
Node processes run with `/data` as their working directory (the image's
`WORKDIR`), and that is where the model's shell commands execute and
relative file paths resolve — **not** `/workspace`. The shell and file
tool descriptions state both paths (the working directory, and the
workspace named by `TURNSTONE_WORKSPACE`), so the model knows to look in
`/workspace` for your files without being told each session.
To make tools start inside the mount instead, override the working
directory on the node services:
```yaml
services:
turnstone-node:
working_dir: /workspace
```
Two caveats before overriding:
- **SQLite fallback**: when a node runs without PostgreSQL, its fallback
database `.turnstone.db` is created in the process working directory.
Changing `working_dir` on an existing SQLite-fallback deployment makes
the node create a fresh database inside the mount and your prior state
appears lost (it is still in the `turnstone-data` volume under `/data`).
The stock compose stacks use PostgreSQL and are unaffected.
- Migrations (`entrypoint.sh`) run in the same working directory, so the
same SQLite caveat applies to them.
## Cleanup
```bash