@divaltor/academy 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -23,13 +23,17 @@ Add Academy to `opencode.jsonc`. All options are optional:
23
23
  {
24
24
  "package": "@divaltor/academy",
25
25
  "options": {
26
- "rudolf": {
27
- "name": "Emperor",
28
- "color": "#8994B8",
29
- "model": "anthropic/claude-opus-4-1#max",
26
+ "agents": {
27
+ "rudolf": {
28
+ "name": "Emperor",
29
+ "color": "#8994B8",
30
+ "model": "anthropic/claude-opus-4-1#max",
31
+ },
30
32
  },
31
- "communication": {
32
- "thread_summary": "opencode-go/glm-5.3-flash",
33
+ "experimental": {
34
+ "communication": {
35
+ "thread_summary": "opencode-go/glm-5.3-flash",
36
+ },
33
37
  },
34
38
  "use_fff": true,
35
39
  },
@@ -38,9 +42,9 @@ Add Academy to `opencode.jsonc`. All options are optional:
38
42
  }
39
43
  ```
40
44
 
41
- Each agent accepts `name`, `color`, and `model`. Colors may be six-digit hex values or OpenCode theme colors. Model references use `provider/model` or `provider/model#variant`.
45
+ Each agent under `agents` accepts `name`, `color`, and `model`. Colors may be six-digit hex values or OpenCode theme colors. Model references use `provider/model` or `provider/model#variant`.
42
46
 
43
- Set `use_fff` to `true` to replace OpenCode's built-in `glob` and `grep` tools globally with [FFF](https://github.com/d-kimuson/fff.nvim) for every agent. This requires a platform supported by `@ff-labs/fff-bun`.
47
+ `use_fff` replaces OpenCode's built-in `glob` and `grep` tools globally with [FFF](https://github.com/d-kimuson/fff.nvim) for every agent. Enabled by default; set to `false` to keep the built-in tools. This requires a platform supported by `@ff-labs/fff-bun`.
44
48
 
45
49
  | Agent | Default model |
46
50
  | ------ | ----------------------------- |
@@ -54,15 +58,15 @@ Set `use_fff` to `true` to replace OpenCode's built-in `glob` and `grep` tools g
54
58
  >
55
59
  > An agent's name is also its session ID. Renaming it can prevent existing sessions from continuing.
56
60
 
57
- Session tools are available under Code Mode's `academy` namespace. Only the root primary session can create threads; omit `agent` for a general coding session.
61
+ Session tools are experimental and off by default; set `experimental.communication` to enable them. They are available under Code Mode's `academy` namespace. Only the root primary session can create threads; omit `agent` for a general coding session.
58
62
 
59
63
  > [!NOTE]
60
64
  >
61
- > `read_thread` with a question summarizes via the `communication.thread_summary` model, or the calling session's model when unset. Models on `opencode-go` cannot use stateless generation (the provider requires session routing headers that only a session request carries), so Academy answers through a dedicated `Academy summary <model>` session via transient `session.generate` — no transcript writes, but the session stays visible in the session list because the plugin API exposes neither archive nor delete for it.
65
+ > `read_thread` with a question summarizes via the `experimental.communication.thread_summary` model, or the calling session's model when unset. Models on `opencode-go` cannot use stateless generation (the provider requires session routing headers that only a session request carries), so Academy answers through a dedicated `Academy summary <model>` session via transient `session.generate` — no transcript writes, but the session stays visible in the session list because the plugin API exposes neither archive nor delete for it.
62
66
 
63
67
  > [!NOTE]
64
68
  >
65
- > `find_thread` currently searches Academy-created sessions only. OpenCode's HTTP client can list persisted sessions, but the plugin context does not yet expose `ctx.session.list`, so Academy cannot safely discover sessions created before the plugin was installed. We are waiting for upstream support rather than reading OpenCode's private SQLite database or connecting back to the server through a separate client.
69
+ > `find_thread` currently searches Academy-created sessions only and is part of the experimental communication tools. OpenCode's HTTP client can list persisted sessions, but the plugin context does not yet expose `ctx.session.list`, so Academy cannot safely discover sessions created before the plugin was installed. We are waiting for upstream support rather than reading OpenCode's private SQLite database or connecting back to the server through a separate client.
66
70
  >
67
71
  > Tracking: [#43517](https://github.com/anomalyco/opencode/issues/43517), [#44155](https://github.com/anomalyco/opencode/issues/44155), [#43556](https://github.com/anomalyco/opencode/pull/43556), and [#46690](https://github.com/anomalyco/opencode/pull/46690).
68
72
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@divaltor/academy",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "An OpenCode plugin that trains a Tracen Academy roster of specialist coding agents to research, build, and review.",
5
5
  "homepage": "https://github.com/divaltor/academy#readme",
6
6
  "bugs": {
@@ -0,0 +1,6 @@
1
+ ## Session collaboration
2
+
3
+ - Academy session tools are available through Code Mode's `academy` namespace. Use Code Mode search when an exact signature is not listed and asynchronous or resumable work would materially help.
4
+ - Only Rudolf creates and controls these sessions. Use native subagents for bounded work whose result is needed immediately; use an Academy thread for independently specifiable work that can proceed concurrently or may need follow-up later. Never delegate the same work through both paths.
5
+ - Give each thread a complete prompt and non-overlapping ownership because it does not inherit this conversation. Retain the returned session ID, continue useful work, and wait only when its result becomes necessary.
6
+ - Use `find_thread` to recover prior Academy work, `send_thread_message` for corrections or added context, and `interrupt_thread` only for obsolete or wrong-premise work. Read and verify the result before synthesizing it for the user.
@@ -29,13 +29,6 @@ You are **Rudolf**, the primary coding agent. Work directly by default and deliv
29
29
  - Give delegates the goal, relevant context, constraints, non-goals, and required result. Parallelize only independent work and never assign overlapping writes.
30
30
  - Verify consequential delegated claims and local fit, inspect delegated changes, and synthesize the user-facing result yourself.
31
31
 
32
- ## Session collaboration
33
-
34
- - Academy session tools are available through Code Mode's `academy` namespace. Use Code Mode search when an exact signature is not listed and asynchronous or resumable work would materially help.
35
- - Only Rudolf creates and controls these sessions. Use native subagents for bounded work whose result is needed immediately; use an Academy thread for independently specifiable work that can proceed concurrently or may need follow-up later. Never delegate the same work through both paths.
36
- - Give each thread a complete prompt and non-overlapping ownership because it does not inherit this conversation. Retain the returned session ID, continue useful work, and wait only when its result becomes necessary.
37
- - Use `find_thread` to recover prior Academy work, `send_thread_message` for corrections or added context, and `interrupt_thread` only for obsolete or wrong-premise work. Read and verify the result before synthesizing it for the user.
38
-
39
32
  # Communication
40
33
 
41
34
  - Default to roughly ten lines of prose per answer. Add structure only when it carries information.
package/src/agents.ts CHANGED
@@ -4,6 +4,8 @@ import cafeSystem from "./agents/cafe.txt" with { type: "text" };
4
4
  import dantsuSystem from "./agents/dantsu.txt" with { type: "text" };
5
5
  import rudolfSystem from "./agents/rudolf.txt" with { type: "text" };
6
6
 
7
+ export { default as rudolfCommunicationPrompt } from "./agents/rudolf-communication.txt" with { type: "text" };
8
+
7
9
  interface Permission {
8
10
  readonly action: string;
9
11
  readonly resource: string;
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Agent, Model, Plugin } from "@opencode/plugin/effect";
2
2
  import { Config, Effect, Option, Redacted, Schema } from "effect";
3
3
 
4
- import { agents } from "./agents";
4
+ import { agents, rudolfCommunicationPrompt } from "./agents";
5
5
  import { FffTools } from "./fff-tools";
6
6
  import { SessionCommunication } from "./session-communication";
7
7
 
@@ -18,13 +18,21 @@ const AgentOptions = Schema.Struct({
18
18
  name: Schema.optionalKey(Schema.NonEmptyString),
19
19
  });
20
20
 
21
- const AcademyOptions = Schema.Struct({
21
+ const AgentsOptions = Schema.Struct({
22
22
  agnes: Schema.optionalKey(AgentOptions),
23
23
  bellno: Schema.optionalKey(AgentOptions),
24
24
  cafe: Schema.optionalKey(AgentOptions),
25
- communication: Schema.optionalKey(SessionCommunication.Options),
26
25
  dantsu: Schema.optionalKey(AgentOptions),
27
26
  rudolf: Schema.optionalKey(AgentOptions),
27
+ });
28
+
29
+ const ExperimentalOptions = Schema.Struct({
30
+ communication: Schema.optionalKey(SessionCommunication.Options),
31
+ });
32
+
33
+ const AcademyOptions = Schema.Struct({
34
+ agents: Schema.optionalKey(AgentsOptions),
35
+ experimental: Schema.optionalKey(ExperimentalOptions),
28
36
  use_fff: Schema.optionalKey(Schema.Boolean),
29
37
  });
30
38
 
@@ -35,12 +43,17 @@ const setup = Effect.fn("Academy.setup")(function* setup(ctx: Plugin.Context) {
35
43
  const options = yield* Schema.decodeUnknownEffect(AcademyOptions, {
36
44
  onExcessProperty: "error",
37
45
  })(ctx.options).pipe(Effect.orDie);
46
+ const agentOptions = options.agents ?? {};
38
47
  const agentNames = new Map(
39
- agents.map((agent) => [agent.name, options[agent.id]?.name ?? agent.name])
48
+ agents.map((agent) => [
49
+ agent.name,
50
+ agentOptions[agent.id]?.name ?? agent.name,
51
+ ])
40
52
  );
41
53
  const agentID = (id: (typeof agents)[number]["id"]) =>
42
- Agent.ID.make(options[id]?.name ?? id);
54
+ Agent.ID.make(agentOptions[id]?.name ?? id);
43
55
  const configuredAgentIDs = agents.map((agent) => agentID(agent.id));
56
+ const communication = options.experimental?.communication;
44
57
 
45
58
  if (new Set(configuredAgentIDs).size !== configuredAgentIDs.length) {
46
59
  return yield* Effect.die(
@@ -64,7 +77,7 @@ const setup = Effect.fn("Academy.setup")(function* setup(ctx: Plugin.Context) {
64
77
  });
65
78
  });
66
79
 
67
- if (options.use_fff) {
80
+ if (options.use_fff ?? true) {
68
81
  yield* FffTools.register(ctx);
69
82
  }
70
83
 
@@ -87,8 +100,15 @@ const setup = Effect.fn("Academy.setup")(function* setup(ctx: Plugin.Context) {
87
100
  agent.description = definition.description;
88
101
  agent.mode = definition.mode;
89
102
  agent.color =
90
- options[definition.id]?.color ?? agent.color ?? definition.color;
91
- agent.system = definition.system.replaceAll(
103
+ agentOptions[definition.id]?.color ?? agent.color ?? definition.color;
104
+ const base =
105
+ definition.id === "rudolf" && communication !== undefined
106
+ ? definition.system.replace(
107
+ "# Communication",
108
+ `${rudolfCommunicationPrompt}\n\n# Communication`
109
+ )
110
+ : definition.system;
111
+ agent.system = base.replaceAll(
92
112
  /\b(?:Rudolf|Agnes|Cafe|Dantsu|Bellno)\b/gu,
93
113
  (name) => agentNames.get(name) ?? name
94
114
  );
@@ -105,7 +125,7 @@ const setup = Effect.fn("Academy.setup")(function* setup(ctx: Plugin.Context) {
105
125
  };
106
126
  });
107
127
  agent.model = parseModel(
108
- options[definition.id]?.model ?? definition.model
128
+ agentOptions[definition.id]?.model ?? definition.model
109
129
  );
110
130
  });
111
131
  }
@@ -113,11 +133,9 @@ const setup = Effect.fn("Academy.setup")(function* setup(ctx: Plugin.Context) {
113
133
  editor.default(agentID("rudolf"));
114
134
  });
115
135
 
116
- yield* SessionCommunication.register(
117
- ctx,
118
- options.communication ?? {},
119
- agentID("rudolf")
120
- );
136
+ if (communication !== undefined) {
137
+ yield* SessionCommunication.register(ctx, communication, agentID("rudolf"));
138
+ }
121
139
  });
122
140
 
123
141
  export default Plugin.define({