delete the AGENTS.md injection; --dir was always the real anchor

The sidecar seeded an AGENTS.md into the serve's project root telling the agent to read its working
directory from the "Working directory for this session" line in its system prompt. The run path sends no
system prompt, so there was no such line and the instruction had been inert since turns moved off the
serve to `opencode run --dir`.

What it was standing in for, `--dir` does properly — tested rather than assumed
(docs/opencode-phase0-review.md): `--dir` anchors the agent's own file operations, not just the process
cwd, and the anchor survives a multi-step turn with a write in the middle. Nothing replaces it.

The generated file is removed from disk too, not only from the code that wrote it; leaving it would have
kept feeding standing instructions to every session while looking, in the source, as though it were gone.

Also corrects the claim that all OpenCode sessions live in one server's project. True when turns
inherited the serve's directory, false now: one serve lists 7 sessions across several directories, which
is why the cwd filter has to read `directory` rather than assume a single one.

docs/opencode-phase0-review.md, item 2 — Phase 1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-10 02:51:20 +00:00
co-authored by Claude Opus 5
parent 0210e85762
commit cfbf58cdba
2 changed files with 23 additions and 27 deletions
+8 -3
View File
@@ -4,9 +4,14 @@ import { getConnection } from './opencode/client';
import { logger } from './logger';
// The OpenCode analog of claude-sessions.ts. OpenCode's own store is the source of truth, read via the
// fixed pm2-managed server's HTTP API (never the DB directly). All OpenCode /chat sessions live in that
// one server's project, so listing is just GET /session. Returns the same shapes as the Claude reader,
// tagged harness:'opencode', so chat.ts can merge both harnesses transparently.
// fixed pm2-managed server's HTTP API (never the DB directly). Returns the same shapes as the Claude
// reader, tagged harness:'opencode', so chat.ts can merge both harnesses transparently.
//
// Sessions do NOT all live in one project. That claim was true when turns went through the serve and
// inherited its directory; turns are now `opencode run --dir <cwd>` subprocesses, so each session
// records the directory it ran in and one serve happily lists sessions across many. Verified: a single
// serve returned 7 sessions spread over several directories, which is also why the cwd filter has to
// read `directory` rather than assume.
/**
* List OpenCode sessions for a working directory, so each context (/chat pwd, email account, project)
+15 -24
View File
@@ -1,4 +1,4 @@
import { mkdirSync, writeFileSync, existsSync, readdirSync, readFileSync, realpathSync } from 'node:fs';
import { mkdirSync, readdirSync, readFileSync, realpathSync } from 'node:fs';
import { homedir } from 'node:os';
import { join } from 'node:path';
import { DATA_PATH } from '../../data-path';
@@ -8,32 +8,18 @@ import type { SidecarCommand, SidecarEvent } from '../protocol';
import { runOpenCodeTurn, killOpenCodeTurn } from './runner';
// The OpenCode sidecar (officer-opencode). Same philosophy as officer-claude: a singleton process that
// OWNS its runtime — here, an `opencode serve` — registers with the API server, and answers commands.
// The serve's working directory is DATA_PATH/opencode_server, which holds an AGENTS.md OpenCode loads
// as standing instructions (the per-chat system prompt just supplies the target cwd). It listens on a
// random port, reported to the API on connect so it can route there.
// OWNS its runtime — here, an `opencode serve` — registers with the API server, and answers commands. It
// listens on a random port, reported to the API on connect so it can route there.
//
// The serve's working directory is DATA_PATH/opencode_server, and that is now ALL it is: turns do not go
// through the serve, they are `opencode run --dir <cwd>` subprocesses (runner.ts). The serve is used for
// session CRUD and model enumeration only.
const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '5000'}`;
const OPENCODE_BIN = process.env.OPENCODE_BIN || join(homedir(), '.opencode', 'bin', 'opencode');
const SERVE_CWD = join(DATA_PATH, 'opencode_server');
const HEALTH_TIMEOUT_MS = 20_000;
// Standing instructions OpenCode loads from the project root (SERVE_CWD/AGENTS.md). Tells the agent to
// take its working directory from the "Working directory for this session" line in the system prompt.
const AGENTS_MD = `# Officer OpenCode Agent
## Working Directory Rules
**CRITICAL**: Always use absolute paths for all file operations.
At the start of each session, read the "Working directory for this session" value from your system prompt. Use that path as the base for all file tool calls (read, write, edit, glob, grep). Never use relative paths or assume the current directory.
Example:
- ✅ \`read /absolute/path/to/project/file.ts\`
- ❌ \`read ./project/file.ts\`
- ❌ \`read file.ts\`
`;
/**
* Kill any orphaned `opencode serve` whose working directory is EXACTLY serveCwd — e.g. one left behind
* by a previous sidecar that exited uncleanly (SIGKILL/crash), where its SIGTERM shutdown handler never
@@ -100,9 +86,14 @@ async function waitHealthy(baseUrl: string, timeoutMs: number): Promise<boolean>
// ── Start the OpenCode server (cwd = DATA_PATH/opencode-sidecar) ──
mkdirSync(SERVE_CWD, { recursive: true });
// Seed the project AGENTS.md before starting the serve (if absent — don't clobber local edits).
const agentsPath = join(SERVE_CWD, 'AGENTS.md');
if (!existsSync(agentsPath)) writeFileSync(agentsPath, AGENTS_MD);
// An AGENTS.md used to be seeded here, telling the agent to read its working directory from the
// "Working directory for this session" line in its system prompt. It was deleted on 2026-08-10 because
// it pointed at nothing: the run path (runner.ts) sends NO system prompt at all, so there was no such
// line to read, and the instruction had been inert since turns moved off the serve.
//
// What it was standing in for, `--dir` does properly. Tested against the installed binary: `--dir`
// anchors the agent's own file operations, not merely the process cwd, and the anchor survives a
// multi-step turn including a write (docs/opencode-phase0-review.md). Nothing replaces this.
// Sweep any orphaned serve from a previous unclean exit (scoped strictly to SERVE_CWD) so we never
// end up with two serves for this directory.