Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .claude/rules/go-layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ cmd/ thin entrypoints only — flag/env parsing, wiring, then
internal/ all real logic. Never imported outside this module.
<feature>/ one package per bounded concern (see "Interfaces" below)
api/ .proto sources — buf's module root (see buf.yaml).
pluggableharness/agent/<category>/v1/*.proto one directory per category per protocol version
pluggableharness/<category>/v1/*.proto one directory per category per protocol version
pkg/ first-class, third-party-consumable Go integration —
the only thing a plugin author needs to import.
<category>/ pkg/model/, pkg/tool/, pkg/context/, pkg/memory/,
Expand Down
6 changes: 3 additions & 3 deletions .claude/rules/proto.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ is derived and never hand-edited (see `plugin-runtime.md`).
## Syntax and packaging

- `syntax = "proto3";` always.
- Package per category, per version: `package pluggableharness.agent.<category>.v1;` — `pluggableharness.agent.model.v1`, `pluggableharness.agent.tool.v1`, `pluggableharness.agent.memory.v1`, `pluggableharness.agent.context.v1`, `pluggableharness.agent.frontend.v1`, `pluggableharness.agent.widget.v1`, `pluggableharness.agent.kernel.v1` (the kernel-callback service). File path mirrors the package under buf's module root (`buf.yaml`'s `api` module): `api/pluggableharness/agent/<category>/v1/<category>.proto`.
- Package per category, per version: `package pluggableharness.<category>.v1;` — `pluggableharness.model.v1`, `pluggableharness.tool.v1`, `pluggableharness.memory.v1`, `pluggableharness.context.v1`, `pluggableharness.frontend.v1`, `pluggableharness.widget.v1`, `pluggableharness.kernel.v1` (the kernel-callback service). File path mirrors the package under buf's module root (`buf.yaml`'s `api` module): `api/pluggableharness/<category>/v1/<category>.proto`.
- `option go_package = "github.com/pluggableharness/agent/pkg/<category>/proto/v1;<category>v1";` on every file — explicit, never inferred, **always** the full module-qualified path (`github.com/pluggableharness/agent/...`). This *is* required, not optional: `protoc-gen-go` embeds `go_package`'s path verbatim into every cross-file Go `import` statement it generates, so any package imported by another proto (which is every shared package in this repo) needs the real importable path or the generated code fails to compile. Getting this backwards — omitting the module prefix on the theory that `out: .` already means repo-root — was an actual bug caught during Wave B integration: it compiled fine for a standalone, never-imported file, then broke the moment a second file imported it, producing `import "pkg/config/proto/v1"` instead of `import "github.com/pluggableharness/agent/pkg/config/proto/v1"`.
- `buf.gen.yaml`'s Go plugins run with `out: .` **and** `opt: module=github.com/pluggableharness/agent`. This `module` option is what reconciles the full-path `go_package` above with landing output at the intended repo-root-relative `pkg/<category>/proto/v1/` instead of a redundant nested `./github.com/pluggableharness/agent/pkg/.../` — it tells `protoc-gen-go` to keep the full path for generated import statements but strip that same prefix when computing where to *write* the file relative to `out`. This is why `api/`'s tree (which mirrors the full dotted package name, `api/pluggableharness/agent/...`) and `pkg/`'s tree (which doesn't) intentionally look different — see `go-layout.md`. Do not drop the `module` opt from `buf.gen.yaml` to "simplify" it — that's the line that makes the split possible.
- `buf.gen.yaml`'s Go plugins run with `out: .` **and** `opt: module=github.com/pluggableharness/agent`. This `module` option is what reconciles the full-path `go_package` above with landing output at the intended repo-root-relative `pkg/<category>/proto/v1/` instead of a redundant nested `./github.com/pluggableharness/agent/pkg/.../` — it tells `protoc-gen-go` to keep the full path for generated import statements but strip that same prefix when computing where to *write* the file relative to `out`. This is why `api/`'s tree (which mirrors the full dotted package name, `api/pluggableharness/...`) and `pkg/`'s tree (which doesn't) intentionally look different — see `go-layout.md`. Do not drop the `module` opt from `buf.gen.yaml` to "simplify" it — that's the line that makes the split possible.

## Strong typing — no ambiguous types

Expand Down Expand Up @@ -69,6 +69,6 @@ strongly typed as the Go code that implements it.
work: an old session must always be decodable by a client built against a
newer proto, because the field numbers and types of a released message
never change.
- A breaking change is shipped as a new package version (`pluggableharness.agent.model.v2`), never as an edit to `v1`. It lands at `api/pluggableharness/agent/model/v2/` and generates into `pkg/model/proto/v2/`, alongside — never replacing — `v1`. The old `v1` service stays defined and generated as long as any retained session was produced by a `v1` plugin.
- A breaking change is shipped as a new package version (`pluggableharness.model.v2`), never as an edit to `v1`. It lands at `api/pluggableharness/model/v2/` and generates into `pkg/model/proto/v2/`, alongside — never replacing — `v1`. The old `v1` service stays defined and generated as long as any retained session was produced by a `v1` plugin.
- Field numbers are never reused, even for removed fields — `reserved N;`
and `reserved "field_name";` on removal.
2 changes: 1 addition & 1 deletion .github/instructions/proto.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ applyTo: "**/*.proto"

The full rules live in `.claude/rules/proto.md` and `plugin-runtime.md`.

- `proto3` syntax; package `pluggableharness.agent.<category>.v1`; `go_package` is fully module-qualified and matched by a `module=` opt in `buf.gen.yaml`.
- `proto3` syntax; package `pluggableharness.<category>.v1`; `go_package` is fully module-qualified and matched by a `module=` opt in `buf.gen.yaml`.
- Strong typing throughout: every enum has a `<NAME>_UNSPECIFIED = 0` value; no `google.protobuf.Any` and no loose string maps — the opaque frontend render payload is the one deliberate carve-out; bounded domains are enums, identifiers are typed messages.
- Every message, field, rpc, and enum carries a doc comment.
- `buf lint` and `buf breaking` must pass. Wire-breaking changes never mutate `v1` in place — they ship as a new `vN` package, and removed field numbers are `reserved`.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
syntax = "proto3";

// Package pluggableharness.agent.common.v1 defines cross-cutting metadata and identity
// Package pluggableharness.common.v1 defines cross-cutting metadata and identity
// types shared by every plugin category's protocol. It has no RPCs of its
// own — it exists purely to be imported. See .claude/rules/proto.md for the
// versioning and typing rules this file follows; a breaking change here is
// a breaking change for every category proto that imports it, so this file
// is held to the same "no breaking changes to a released version" rule as
// everything else, with a wider blast radius.
package pluggableharness.agent.common.v1;
package pluggableharness.common.v1;

option go_package = "github.com/pluggableharness/agent/pkg/common/proto/v1;commonv1";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
syntax = "proto3";

// Package pluggableharness.agent.config.v1 defines the on-the-wire config-schema
// Package pluggableharness.config.v1 defines the on-the-wire config-schema
// advertisement described in specifications/configuration.md §4. This is
// what a provider returns alongside its capabilities so the kernel knows
// what agent.hcl fields it accepts; the schema-to-cty bridge itself (HCL
// decoding, hcldec, cty.Value) is kernel-internal and never crosses the
// wire — Configure receives already-decoded JSON, not HCL or cty.
package pluggableharness.agent.config.v1;
package pluggableharness.config.v1;

option go_package = "github.com/pluggableharness/agent/pkg/config/proto/v1;configv1";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
syntax = "proto3";

// Package pluggableharness.agent.content.v1 defines the canonical content-block message
// Package pluggableharness.content.v1 defines the canonical content-block message
// schema described in specifications/model.md §5 — the state backend's
// source of truth for conversation history (state-backend.md §5's `message`
// event kind). Every model-provider adapter translates its own vendor
Expand All @@ -17,7 +17,7 @@ syntax = "proto3";
// shape (a labeled, stability-tagged list of ContentBlocks) that a second
// consumer besides the context protocol needs to reference without
// depending on the whole context.v1 package.
package pluggableharness.agent.content.v1;
package pluggableharness.content.v1;

import "google/protobuf/struct.proto";

Expand Down Expand Up @@ -118,7 +118,7 @@ message ToolUseBlock {
string name = 2;

// The call's arguments, already parsed — conforms to the tool's
// input_schema (pluggableharness.agent.schema.v1.Schema). A Struct because the shape is
// input_schema (pluggableharness.schema.v1.Schema). A Struct because the shape is
// genuinely runtime-defined per tool, not fixed at the proto level (see
// .claude/rules/proto.md's Struct carve-out).
google.protobuf.Struct arguments = 3;
Expand Down Expand Up @@ -202,7 +202,7 @@ message DocumentBlock {

// Stability hints whether a ContextSection's content changes turn to turn,
// used both as a context provider's ContextCapabilities-level declaration
// (pluggableharness.agent.context.v1.ContextCapabilities.stability) and
// (pluggableharness.context.v1.ContextCapabilities.stability) and
// per ContextSection below. context.md §7: this is a direct translation of
// the research's strongest cross-cutting finding — harnesses converge on a
// tools -> system -> static-project-context -> conversation-tail prefix
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
syntax = "proto3";

// Package pluggableharness.agent.context.v1 defines the context provider plugin protocol
// Package pluggableharness.context.v1 defines the context provider plugin protocol
// described in specifications/context.md — plugins that hook
// context-assemble and contribute content to the prompt before each model
// call (e.g. a CLAUDE.md reader, an AGENTS.md reader, a git-status/file-tree
// summarizer). See .claude/rules/proto.md.
//
// ContextSection and Stability — the section chain this protocol assembles
// and the turn-to-turn-change hint each section carries — are defined in
// pluggableharness.agent.content.v1, not here: that chain is consumed by
// pluggableharness.content.v1, not here: that chain is consumed by
// both this protocol and the model provider's completion request
// (forthcoming), so it's homed alongside content.v1's other shared content
// shapes rather than duplicated or owned by only one consumer.
package pluggableharness.agent.context.v1;
package pluggableharness.context.v1;

import "google/protobuf/struct.proto";
import "pluggableharness/agent/common/v1/common.proto";
import "pluggableharness/agent/config/v1/config.proto";
import "pluggableharness/agent/content/v1/content.proto";
import "pluggableharness/agent/model/v1/model.proto";
import "pluggableharness/agent/render/v1/render.proto";
import "pluggableharness/agent/slashcommand/v1/slashcommand.proto";
import "pluggableharness/common/v1/common.proto";
import "pluggableharness/config/v1/config.proto";
import "pluggableharness/content/v1/content.proto";
import "pluggableharness/model/v1/model.proto";
import "pluggableharness/render/v1/render.proto";
import "pluggableharness/slashcommand/v1/slashcommand.proto";

option go_package = "github.com/pluggableharness/agent/pkg/context/proto/v1;contextv1";

Expand Down Expand Up @@ -110,7 +110,7 @@ message ContextCapabilities {

// Whether this provider's contributed content changes turn to turn. MUST
// be set. context.md §2, §7.
pluggableharness.agent.content.v1.Stability stability = 2;
pluggableharness.content.v1.Stability stability = 2;

// Whether this provider acts as a compactor: MAY rewrite, merge, or drop
// other providers' sections in the chain it receives, and MAY receive
Expand All @@ -120,11 +120,11 @@ message ContextCapabilities {

// Slash commands this provider contributes. MAY be empty.
// context.md §2, configuration.md §5.
repeated pluggableharness.agent.slashcommand.v1.SlashCommandSpec slash_commands = 4;
repeated pluggableharness.slashcommand.v1.SlashCommandSpec slash_commands = 4;

// This provider's agent.hcl config schema, advertised so the kernel knows
// what fields Configure accepts. configuration.md §4.
pluggableharness.agent.config.v1.ConfigSchema config_schema = 5;
pluggableharness.config.v1.ConfigSchema config_schema = 5;

// Which hook points (agent-loop/hook-dispatch.md) this provider declares
// HookSubscriberService.DispatchHook subscriptions for, advertised
Expand All @@ -134,7 +134,7 @@ message ContextCapabilities {
// hook.v1 — hook.v1 imports this package's model/tool/plan dependencies,
// so a category capability message importing hook.v1 directly would
// cycle back through it; common.v1 is the shared leaf package instead.
repeated pluggableharness.agent.common.v1.HookPoint supported_hook_points = 6;
repeated pluggableharness.common.v1.HookPoint supported_hook_points = 6;
}

// ConfigureResponse is empty on success. A Configure failure (e.g. a
Expand Down Expand Up @@ -167,7 +167,7 @@ message ContextRequest {
// The model this contribution is being assembled for, so the provider can
// tailor content (and compute tokens against the right budget) for the
// model that will actually consume it. MUST be set. context.md §4.
pluggableharness.agent.model.v1.ModelTarget model_target = 5;
pluggableharness.model.v1.ModelTarget model_target = 5;

// Paths touched so far this session, enabling JIT-scoped contributions
// (e.g. a subdirectory-scoped convention-file reader). MAY be empty, e.g.
Expand All @@ -180,14 +180,14 @@ message ContextRequest {
// The accumulated output of earlier providers in this hook's
// declaration-order chain. MUST be set (MAY be empty on the first
// provider in the chain). context.md §4, §5.
repeated pluggableharness.agent.content.v1.ContextSection prior_sections = 8;
repeated pluggableharness.content.v1.ContextSection prior_sections = 8;

// The session's conversation history. Populated ONLY for a provider whose
// ContextCapabilities.compactor == true; a non-compactor provider MUST
// NOT receive this — for those providers this field arrives empty, which
// is indistinguishable from (and semantically equivalent to) "not
// provided". context.md §5.1.
repeated pluggableharness.agent.content.v1.Message conversation_history = 9;
repeated pluggableharness.content.v1.Message conversation_history = 9;

// The current conversation-history token total, kernel-computed. One of
// the two signals a compactor provider needs to decide WHEN to compact
Expand All @@ -207,15 +207,15 @@ message ContextRequest {
message ContextContribution {
// The full accumulated chain, in declaration order, including this
// provider's own new or updated section(s).
repeated pluggableharness.agent.content.v1.ContextSection sections = 1;
repeated pluggableharness.content.v1.ContextSection sections = 1;

// The session's conversation history, rewritten to replace what was sent
// in ContextRequest.conversation_history. MAY be included by a compactor
// provider (ContextCapabilities.compactor == true) alongside its section
// contribution. When present, the kernel MUST replace the turn's
// conversation history with this before the next model call. context.md
// §5.1.
repeated pluggableharness.agent.content.v1.Message rewritten_history = 2;
repeated pluggableharness.content.v1.Message rewritten_history = 2;
}

// ContextErrorCategory classifies a context provider's failures.
Expand Down Expand Up @@ -282,7 +282,7 @@ message RenderRequest {
message RenderResponse {
// The rendered tree, per the general Emit->Render->Paint pipeline
// (frontend.md §1). context.md §9.
pluggableharness.agent.render.v1.RenderTree tree = 1;
pluggableharness.render.v1.RenderTree tree = 1;
}

// DescribeRequest carries no fields — Describe takes no request-scoped
Expand All @@ -294,5 +294,5 @@ message DescribeRequest {}
message DescribeResponse {
// This plugin build's identity: name, version, source, category,
// protocol_version.
pluggableharness.agent.common.v1.ProducerRef producer = 1;
pluggableharness.common.v1.ProducerRef producer = 1;
}
Loading