Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,10 +231,11 @@ a script.

## Providers

CodeAlmanac uses `almanac-yoke` as its single provider boundary. Codex runs
through app-server; Claude uses Yoke's default Claude surface (currently the
Python Agent SDK). Existing Codex or Claude Code OAuth sessions are reused, and
API credentials can be supplied through Yoke when embedding the SDK.
CodeAlmanac uses `almanac-yoke` as its provider boundary for Codex and Claude.
OpenCode runs through a first-class CLI harness adapter (`opencode run`). Codex
runs through app-server; Claude uses Yoke's default Claude surface (currently the
Python Agent SDK). Existing Codex, Claude Code, or OpenCode OAuth sessions are
reused where available.

Build, ingest, and garden are packaged as a Yoke agent collection under
`src/codealmanac/agents/`. Each agent uses Yoke's native folder contract:
Expand All @@ -247,6 +248,8 @@ native Claude or Codex execution still decides how and when to use them.
```bash
codex login
claude auth login
# OpenCode: install the CLI and authenticate your preferred model provider
opencode auth login
codealmanac doctor
```

Expand Down
2 changes: 1 addition & 1 deletion almanac/architecture/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ For authored wiki contracts, read [Page identity](wiki/page-identity), [Path nor

## Edges And Interfaces

Use [CLI adapter boundary](cli/adapter-boundary) and [Terminal output](cli/terminal-output) for command entrypoints and rendering [@cli-adapter] [@terminal-output]. Use [Agent runs](agent-runs/) for normalized harness execution and the Yoke harness boundary that serves Claude and Codex behind one adapter [@agent-runs]. Use [Source resolution and runtime](sources/source-resolution-and-runtime) when changing ingest inputs or source adapters [@source-runtime].
Use [CLI adapter boundary](cli/adapter-boundary) and [Terminal output](cli/terminal-output) for command entrypoints and rendering [@cli-adapter] [@terminal-output]. Use [Agent runs](agent-runs/) for normalized harness execution: Yoke for Claude and Codex, and the native OpenCode CLI harness [@agent-runs]. Use [Source resolution and runtime](sources/source-resolution-and-runtime) when changing ingest inputs or source adapters [@source-runtime].

[Agents and manuals](runtime-resources/prompts-and-manuals) covers the packaged Yoke agents and writing references used by lifecycle runs, [Setup automation and update](setup/automation-and-update) covers setup-owned scheduler and update behavior, and [Telemetry](telemetry) covers the narrow remote product-signal exception [@agents-manuals] [@setup-automation] [@telemetry]. [Instruction installation](setup/instruction-installation) covers the sibling setup concern: writing `CLAUDE.md`/`AGENTS.md` kernel instructions into a repository [@instruction-install].

Expand Down
10 changes: 7 additions & 3 deletions almanac/architecture/agent-runs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ sources:
- id: provider-adapters
type: file
path: almanac/architecture/agent-runs/provider-adapters.md
note: Architecture page for the Yoke harness boundary that implements the contract.
note: Architecture page for the Yoke harness boundary (Claude and Codex).
- id: opencode-harness
type: file
path: almanac/architecture/agent-runs/opencode-harness.md
note: Architecture page for the native OpenCode CLI harness.
- id: topics
type: file
path: almanac/topics.yaml
Expand All @@ -18,15 +22,15 @@ sources:

# Agent Runs

Agent runs is the part of CodeAlmanac that executes one build, ingest, or garden task against an external coding agent and turns the result into durable, provider-neutral facts. The neighborhood has two pages: the contract lifecycle workflows depend on, and the one adapter that currently implements it [@topics].
Agent runs is the part of CodeAlmanac that executes one build, ingest, or garden task against an external coding agent and turns the result into durable, provider-neutral facts. The neighborhood has three pages: the contract lifecycle workflows depend on, the Yoke adapter for Claude and Codex, and the native OpenCode CLI harness [@topics].

Read this hub when changing how a `RunHarnessRequest` is built, how a harness run reports readiness or results, or how provider events become normalized job-log events.

## Reading Order

Start with [Harness contract](harness-contract). It defines `RunHarnessRequest`, `HarnessRunResult`, and `HarnessEvent`, and states the boundary rule that lifecycle workflows may depend only on those normalized shapes [@harness-contract].

Then read [Yoke harness boundary](provider-adapters). It explains `YokeHarnessAdapter`, the single provider integration that implements the contract for both Claude and Codex by loading a packaged Yoke agent and projecting Yoke runs and events into the contract's models [@provider-adapters].
Then read [Yoke harness boundary](provider-adapters) for Claude and Codex, and [OpenCode harness](opencode-harness) for the separate OpenCode CLI path [@provider-adapters] [@opencode-harness].

## Neighboring Pages

Expand Down
4 changes: 2 additions & 2 deletions almanac/architecture/agent-runs/harness-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ sources:

# Harness Contract

The harness contract is the boundary between lifecycle workflows and external agent providers. Build, ingest, and garden send one normalized `RunHarnessRequest` to a selected harness and receive one normalized `HarnessRunResult` back. The workflows do not know whether the provider is Codex or Claude; that provider-specific behavior lives behind the [Yoke harness boundary](provider-adapters) [@harness-ports] [@harness-requests].
The harness contract is the boundary between lifecycle workflows and external agent providers. Build, ingest, and garden send one normalized `RunHarnessRequest` to a selected harness and receive one normalized `HarnessRunResult` back. The workflows do not know whether the provider is Codex, Claude, or OpenCode; provider-specific behavior lives behind adapters ([Yoke harness boundary](provider-adapters) for Codex/Claude, [OpenCode harness](opencode-harness) for OpenCode) [@harness-ports] [@harness-requests].

The contract matters because lifecycle operations need stable facts after an agent run: readiness, terminal status, output text, optional changed files, transcript references, and normalized events. Those facts feed the [operation runner](../lifecycle/operation-runner), the [run ledger](../../concepts/run-ledger), and user-facing job logs without leaking provider JSON streams into the rest of the system [@harness-results] [@harness-events].

Expand All @@ -34,7 +34,7 @@ A harness adapter has one `kind`, a `check()` method, and a `run()` method [@har

`HarnessesService` indexes adapters by kind and rejects duplicate adapters [@harness-service]. Workflows call `ensure_ready` and `run_ready` as separate stages: readiness failures can include a repair hint and a command to switch harnesses, while every exception from the approved adapter invocation is a provider-execution failure. `run_ready` also wraps caller event-sink failures in `HarnessEventSinkFailed`, keeping local run-log persistence failures distinct from provider failures that occur during the same call [@harness-service].

The supported harness kinds are currently `codex` and `claude`. The terminal run statuses are `succeeded`, `failed`, and `cancelled` [@harness-kinds].
The supported harness kinds are currently `codex`, `claude`, and `opencode`. The terminal run statuses are `succeeded`, `failed`, and `cancelled` [@harness-kinds].

## Run Requests

Expand Down
73 changes: 73 additions & 0 deletions almanac/architecture/agent-runs/opencode-harness.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
---
title: OpenCode Harness
topics: [architecture, harnesses, agent-runs]
sources:
- id: adapter
type: file
path: src/codealmanac/integrations/harnesses/opencode/adapter.py
- id: events
type: file
path: src/codealmanac/integrations/harnesses/opencode/events.py
- id: models
type: file
path: src/codealmanac/integrations/harnesses/opencode/models.py
- id: defaults
type: file
path: src/codealmanac/integrations/harnesses/__init__.py
- id: contract
type: file
path: src/codealmanac/services/harnesses/ports.py
- id: setup
type: file
path: src/codealmanac/integrations/setup/opencode.py
- id: tests
type: file
path: tests/test_opencode_harness.py
---

# OpenCode Harness

## What It Owns

`OpenCodeHarnessAdapter` is a first-class harness integration that runs
lifecycle jobs through the local OpenCode CLI (`opencode run`), not through
Yoke [@adapter] [@defaults]. It implements the same service-owned harness port
as Yoke adapters: `check()` readiness and `run()` execution returning a
normalized `HarnessRunResult` [@contract] [@adapter].

Unlike Codex/Claude, OpenCode model ids are dynamic `provider/model` strings
(possibly with nested segments such as `openrouter/z-ai/glm-5`). The adapter
and config accept any well-formed OpenCode model id; listing uses `opencode
models` when the CLI is present [@models].

## Project Agents

Before each lifecycle run, the adapter stages a packaged primary agent under
the product-owned path `runtime_root/opencode/agents/codealmanac-<agent>.md`
(typically under `~/.codealmanac/harnesses/opencode/agents/`), never the target
repository's tree. The CLI is invoked with `OPENCODE_CONFIG_DIR` pointing at
that directory — OpenCode loads it as an additive agents/commands source after
global and project config, so user auth and providers stay on the real OpenCode
config while generated agents stay out of `git status` [@adapter]. The runtime
prompt is supplied on stdin [@adapter].

## Events And Transcripts

JSON lines from `opencode run` are projected into CodeAlmanac harness events
by `integrations/harnesses/opencode/events.py` [@events]. Live runs may
surface a session id for job linkage. Separate from live runs, sync discovers
historical OpenCode sessions from OpenCode's SQLite store
(`~/.local/share/opencode/opencode.db`); see
[Source resolution and runtime](../sources/source-resolution-and-runtime).

## Setup

`codealmanac setup` can install guide text into OpenCode's global
`~/.config/opencode/AGENTS.md` as a managed block [@setup]. Uninstall removes
that block without touching unrelated user content [@setup].

## Related Pages

See [Harness contract](harness-contract), [Yoke harness boundary](provider-adapters),
[Instruction installation](../setup/instruction-installation), and
[Controlled model catalog](../../decisions/controlled-model-catalog).
25 changes: 14 additions & 11 deletions almanac/architecture/agent-runs/provider-adapters.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,26 +32,29 @@ sources:

## What It Owns

CodeAlmanac has one provider integration: `YokeHarnessAdapter`. It implements
the service-owned harness port for Claude and Codex, selects CodeAlmanac's Yoke
CodeAlmanac has two harness integrations. `YokeHarnessAdapter` implements the
service-owned harness port for Claude and Codex, selects CodeAlmanac's Yoke
surface and run options, and converts Yoke runs and events into durable product
models [@adapter] [@contract].
models [@adapter] [@contract]. OpenCode does **not** go through Yoke; it has a
separate native CLI adapter documented in [OpenCode harness](opencode-harness).
`YokeHarnessAdapter` raises if asked to run `HarnessKind.OPENCODE`.

The adapter explicitly selects Codex app-server and leaves Claude on Yoke's
default Claude surface. It loads the requested build, ingest, or garden agent
from the packaged Yoke collection, forwards the task prompt unchanged, and
The Yoke adapter explicitly selects Codex app-server and leaves Claude on
Yoke's default Claude surface. It loads the requested build, ingest, or garden
agent from the packaged Yoke collection, forwards the task prompt unchanged, and
applies the trusted non-interactive permission and timeout policy [@adapter].

## Runtime Root

`YokeHarnessAdapter` also requires a `runtime_root: Path` at construction.
`default_harness_adapters(runtime_root)` receives `LocalStatePaths.harness_runtime_dir`
(`state_dir / "harnesses"`, so `~/.codealmanac/harnesses` by default) from the
composition root and passes it to both the Claude and Codex adapters [@defaults]
[@settings] [@app]. The adapter forwards `runtime_root` into `yoke.Harness(...)`
on every `check()` and `run()` call, so Yoke's native provider compilation caches
under CodeAlmanac's own local state instead of writing `.codex` or `.claude`
configuration into the target repository [@adapter].
composition root and passes it to the Claude and Codex Yoke adapters and the
OpenCode native adapter [@defaults] [@settings] [@app]. The Yoke adapter
forwards `runtime_root` into `yoke.Harness(...)` on every `check()` and `run()`
call, so Yoke's native provider compilation caches under CodeAlmanac's own local
state instead of writing `.codex` or `.claude` configuration into the target
repository [@adapter].

`check()` cannot run a real repository task, so it uses a sibling working
directory instead of the caller's `cwd`: `runtime_root.parent / "readiness"`
Expand Down
2 changes: 1 addition & 1 deletion almanac/architecture/composition-root.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ The composition root is `src/codealmanac/app.py`. It is the one place that build

`AppAdapters` is the injection surface for outside systems. It can carry harness adapters, transcript discovery adapters, source runtime adapters, a scheduler, a worker spawner, an executor spawner, a process controller, update command providers, instruction installers, global-state removers, and package uninstallers [@app-root]. Each field is optional, so production construction can use defaults while tests can pass fakes.

This design keeps provider selection at the edge. If no harness adapters are supplied, the root uses `default_harness_adapters(local_state.harness_runtime_dir)`, giving both the Claude and Codex adapters a product-owned cache directory under local state instead of the target repository [@app-root]. If no scheduler is supplied, automation uses `LaunchdSchedulerAdapter`. If no source adapters are supplied, the source service receives the default transcript discovery and runtime adapters [@app-root]. The services receive port implementations, not provider-specific branching logic. See [Yoke harness boundary](agent-runs/provider-adapters) for what the runtime directory is used for.
This design keeps provider selection at the edge. If no harness adapters are supplied, the root uses `default_harness_adapters(local_state.harness_runtime_dir)`, giving the Claude and Codex Yoke adapters and the native OpenCode adapter a product-owned cache directory under local state instead of the target repository [@app-root]. If no scheduler is supplied, automation uses `LaunchdSchedulerAdapter`. If no source adapters are supplied, the source service receives the default transcript discovery and runtime adapters [@app-root]. The services receive port implementations, not provider-specific branching logic. See [Yoke harness boundary](agent-runs/provider-adapters) for what the runtime directory is used for.

## Service Assembly

Expand Down
19 changes: 15 additions & 4 deletions almanac/architecture/setup/instruction-installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ sources:
type: file
path: src/codealmanac/integrations/setup/codex.py
note: Codex installation mechanics (managed block inside AGENTS.md).
- id: opencode-installer
type: file
path: src/codealmanac/integrations/setup/opencode.py
note: OpenCode installation mechanics (managed block inside global AGENTS.md).
- id: managed-blocks
type: file
path: src/codealmanac/integrations/setup/managed_blocks.py
Expand Down Expand Up @@ -96,8 +100,8 @@ package stable Yoke agent instructions and expose the manuals those operations u

## Per-Target Mechanics

Claude and Codex are installed differently because their global instruction
files have different conventions.
Claude, Codex, and OpenCode are installed differently because their global
instruction files have different conventions.

For Claude, the installer writes the guide verbatim to a dedicated file,
`~/.claude/codealmanac.md`, and ensures `~/.claude/CLAUDE.md` contains a
Expand All @@ -115,9 +119,16 @@ non-empty content, otherwise `~/.codex/AGENTS.md` [@codex-installer].
Uninstall removes the managed block from both possible paths and deletes a
file that becomes empty [@codex-installer].

Both installers report an `InstructionChange` naming the `SetupTarget`,
For OpenCode, the installer upserts the guide into OpenCode's own global
rules file, `~/.config/opencode/AGENTS.md`, using the same managed-block
markers as Codex [@opencode-installer] [@managed-blocks]. There is no
override-file convention — only that one path is targeted. Uninstall removes
the managed block and deletes the file if it becomes empty
[@opencode-installer].

All installers report an `InstructionChange` naming the `SetupTarget`,
whether anything changed, which paths were touched, and a human-readable
message, so `setup`'s JSON and interactive output can say "already installed"
without rewriting untouched files [@setup-models] [@claude-installer]
[@codex-installer]. This mirrors the idempotence expectations described in
[@codex-installer] [@opencode-installer]. This mirrors the idempotence expectations described in
[Setup automation and update](automation-and-update).
12 changes: 11 additions & 1 deletion almanac/architecture/sources/source-resolution-and-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ sources:
type: file
path: src/codealmanac/integrations/sources/transcripts/claude.py
note: Claude transcript discovery path and metadata parsing.
- id: opencode_transcripts
type: file
path: src/codealmanac/integrations/sources/transcripts/opencode.py
note: OpenCode transcript discovery via shared SQLite session table.
- id: opencode_transcript_runtime
type: file
path: src/codealmanac/integrations/sources/transcripts/opencode_runtime.py
note: OpenCode transcript runtime reading message/part tables.
- id: sync_evaluation
type: file
path: src/codealmanac/workflows/sync/evaluation.py
Expand Down Expand Up @@ -54,10 +62,12 @@ Ingest uses this boundary before it renders the writing prompt. It resolves the

## Transcript Discovery

Transcript discovery is a separate source path used by sync. The default discovery set has two adapters: Claude and Codex [@transcript_adapters]. The source model has the same two transcript app values, `claude` and `codex`, so there is no separate app identity for Codex app, Claude Desktop, Claude web, or editor-specific surfaces [@source_models].
Transcript discovery is a separate source path used by sync. The default discovery set has three adapters: Claude, Codex, and OpenCode [@transcript_adapters]. The source model has matching transcript app values `claude`, `codex`, and `opencode` [@source_models].

The Codex adapter scans `.codex/sessions` under the configured home directory, reads the first JSONL lines for session metadata, and skips transcripts whose metadata marks `thread_source` as `subagent` [@codex_transcripts]. The Claude adapter scans `.claude/projects` under the configured home directory and skips paths that contain `subagents` [@claude_transcripts].

OpenCode has no per-session file to scan — session history lives as rows in one shared SQLite database (`~/.local/share/opencode/opencode.db`). Its discovery adapter queries that database's `session` table with `parent_id IS NULL` to exclude sub-agent sessions [@opencode_transcripts]. Because there is no file per session, `TranscriptCandidate.transcript_path` holds a synthetic identity (`opencode-session:<id>`) rather than a filesystem path. `OpencodeTranscriptRuntimeAdapter` recognizes that scheme and resolves content from the database's `message`/`part` tables [@opencode_transcript_runtime]. It is registered ahead of the generic file-based transcript runtime adapter so dispatch order remains correct [@transcript_adapters].

Sync does not ingest every discovered transcript. It matches each transcript `cwd` to a registered repository root, skips unregistered working directories, and skips transcripts older than the active sync window as `inactive` [@sync_evaluation]. That means a transcript can be discovered correctly but still not become ingest input for the current sync run.

## Related Reference
Expand Down
Loading