Skip to content

Commit 8d3b9f2

Browse files
authored
Updated readme docs to represent v2 capabilities in place. (#42)
* Readme and sample 01 * Updated documents for DiscordAgent
1 parent 768f085 commit 8d3b9f2

File tree

3 files changed

+391
-109
lines changed

3 files changed

+391
-109
lines changed

README.md

Lines changed: 264 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,104 +1,264 @@
1-
# Coven
2-
3-
A tiny, composable **.NET 10** engine for orchestrating multiple agents to achieve big things.
4-
5-
> ___"With great power comes great responsibility"___ - _Uncle Ben_
6-
> <br> If you use this library, don't be evil.
7-
8-
## Highlights
9-
10-
* **Typed MagikBlocks**: implement `IMagikBlock<TIn,TOut>.DoMagik(...)` and compose work as pure(ish) functions.
11-
* **Tag‑based routing**: a per‑ritual tag scope steers selection; blocks may also advertise **capabilities**.
12-
* **DI‑first**: one builder on `IServiceCollection` (`BuildCoven`) with `MagikBlock<…>` and `LambdaBlock<…>` helpers; finish with `.Done(pull?: bool)`.
13-
* **Journal Primitives**: reliable, distributable, and seamless to developers. Scriveners MUST support a long position.
14-
* **Spellcasting (optional)**: minimal `ISpell<…>` interfaces + JSON‑schema generation for tool contracts.
15-
16-
## Quick Start
17-
18-
### 1) Hello, MagikBlocks (DI)
19-
20-
```csharp
21-
using Microsoft.Extensions.DependencyInjection;
22-
using Coven.Core;
23-
using Coven.Core.Builder;
24-
25-
sealed class BuildCodes : IMagikBlock<Empty, int[]>
26-
{
27-
public Task<int[]> DoMagik(Empty _, CancellationToken ct = default)
28-
{
29-
int[] codes = new int[] {
30-
73, 102, 32, 111, 110, 108, 121, 32, 73, 32, 99, 111, 117, 108, 100, 32, 98, 101, 32, 115, 111, 32, 103, 114, 111, 115, 115, 108, 121, 32, 105, 110, 99, 97, 110, 100, 101, 115, 99, 101, 110, 116, 46
31-
};
32-
return Task.FromResult(codes);
33-
}
34-
}
35-
36-
sealed class CodesToChars : IMagikBlock<int[], char[]>
37-
{
38-
public Task<char[]> DoMagik(int[] codes, CancellationToken ct = default)
39-
=> Task.FromResult(codes.Select(c => (char)c).ToArray());
40-
}
41-
42-
sealed class JoinChars : IMagikBlock<char[], string>
43-
{
44-
public Task<string> DoMagik(char[] chars, CancellationToken ct = default) => Task.FromResult(new string(chars));
45-
}
46-
47-
var services = new ServiceCollection();
48-
services.BuildCoven(b =>
49-
{
50-
b.MagikBlock<Empty, int[], BuildCodes>();
51-
b.MagikBlock<int[], char[], CodesToChars>();
52-
b.MagikBlock<char[], string, JoinChars>();
53-
b.Done();
54-
});
55-
56-
using var sp = services.BuildServiceProvider();
57-
58-
// Avoid GetRequiredService in production code (unless you know exactly what you are doing).
59-
// Here we use it simply to keep the sample small and clear, but in production you should use a hosted service to run rituals.
60-
var coven = sp.GetRequiredService<ICoven>();
61-
var result = await coven.Ritual<string>();
62-
63-
Console.WriteLine(result); //If only I could be so grossly incandescent.
64-
```
65-
66-
---
67-
68-
## Repository Layout
69-
70-
* **src/Coven.Core/** — runtime
71-
* **src/Coven.Core.Tests/** — tests for core
72-
* **src/Coven.Spellcasting/** — minimal spellcasting layer
73-
* **src/Coven.Chat/** — chat primitives
74-
* **architecture/** — flat architecture docs (see below)
75-
* **build/** — CI/release scripts
76-
* **INDEX.md**, **README.md**, **CONTRIBUTING.md**, **AGENTS.md**, license files in repo root
77-
78-
## Documentation
79-
80-
Start here:
81-
82-
* **Architecture Guide**[`/architecture/README.md`](/architecture/README.md)
83-
* **Core**[`/architecture/Coven.Core.md`](/architecture/Coven.Core.md)
84-
* **Spellcasting**[`/architecture/Coven.Spellcasting.md`](/architecture/Coven.Spellcasting.md)
85-
* **Chat**[`/architecture/Coven.Chat.md`](/architecture/Coven.Chat.md)
86-
* **Daemonology (hosts)**[`/architecture/Coven.Daemonology.md`](/architecture/Coven.Daemonology.md)
87-
* **Integrations (docs only)**[`/architecture/Coven.Codex.md`](/architecture/Coven.Codex.md), [`/architecture/Coven.OpenAI.md`](/architecture/Coven.OpenAI.md), [`/architecture/Coven.Spellcasting.MCP.md`](/architecture/Coven.Spellcasting.MCP.md)
88-
89-
---
90-
91-
## Licensing
92-
93-
**Dual‑license (BUSL‑1.1 + Commercial):**
94-
95-
* **Community**: Business Source License 1.1 (BUSL‑1.1) with an Additional Use Grant permitting Production Use if you and your affiliates made **< US $100M** in combined gross revenue in the prior fiscal year. See `LICENSE`.
96-
* **Commercial/Enterprise**: available under a separate agreement. See `COMMERCIAL-TERMS.md`.
97-
98-
*Change Date/License*: `LICENSE` specifies a Change License of **MIT** on **2029‑09‑11**.
99-
100-
## Support
101-
102-
* Patreon: [https://www.patreon.com/c/Goldenwitch](https://www.patreon.com/c/Goldenwitch)
103-
104-
> © 2025 Autumn Wyborny. BUSL 1.1, free for non-profits, individuals, and commercial business under 100m annual revenue.
1+
# Coven
2+
3+
A minimal, composable **.NET 10** engine for orchestrating multiple agents to achieve big things.
4+
5+
> ___"With great power comes great responsibility"___ - _Uncle Ben_
6+
> <br> If you use this library, don't be evil.
7+
8+
## Covenants
9+
10+
* **Journal or it didn't happen** Every thought and output lands in a Scrivener for replay, audit, and time-travel.
11+
* **Compile time validation is better than vibes** Designed from the ground up to minimize side-effects.
12+
* **Daemons behave** Lifecycle, backpressure, graceful shutdown. Async and long-running by design.
13+
* **Hosts over ceremony** Use generic host and DI patterns to painlessly replace or extend functionality.
14+
* **Window/Shatter** Semantic windowing over streamed chats and agents.
15+
16+
## Quick Start
17+
Run Sample 01 (Discord Agent) to see Coven orchestrate a Discord chat channel with an OpenAI‑backed agent.
18+
19+
See detailed steps: [Sample 01 — Discord Agent README](src/samples/01.DiscordAgent/README.md).
20+
21+
- Prerequisites:
22+
- .NET 10 SDK installed.
23+
- Discord Bot: token provisioned, bot invited to your server, Message Content Intent enabled in the Discord Developer Portal, and permission to read/write in a target channel.
24+
- Channel ID: enable Discord Developer Mode, right‑click the target channel → Copy ID.
25+
- OpenAI API key and a valid model (for example, `gpt-5-2025-08-07`).
26+
27+
### 1) Configure secrets (env vars or defaults)
28+
29+
- Easiest: set environment variables and keep `Program.cs` unchanged:
30+
- `DISCORD_BOT_TOKEN`
31+
- `DISCORD_CHANNEL_ID` (unsigned integer)
32+
- `OPENAI_API_KEY`
33+
- `OPENAI_MODEL` (defaults to `gpt-5-2025-08-07` if not set)
34+
- Or edit defaults at the top of `src/samples/01.DiscordAgent/Program.cs` (they’re used only if env vars are absent).
35+
36+
Example from Sample 01 (`Program.cs`):
37+
38+
```csharp
39+
// Defaults used if env vars are not present
40+
string defaultDiscordToken = ""; // set your Discord bot token
41+
ulong defaultDiscordChannelId = 0; // set your channel id
42+
string defaultOpenAiApiKey = ""; // set your OpenAI API key
43+
string defaultOpenAiModel = "gpt-5-2025-08-07"; // choose the model
44+
45+
// Environment overrides (optional)
46+
string? envDiscordToken = Environment.GetEnvironmentVariable("DISCORD_BOT_TOKEN");
47+
string? envDiscordChannelId = Environment.GetEnvironmentVariable("DISCORD_CHANNEL_ID");
48+
string? envOpenAiApiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY");
49+
string? envOpenAiModel = Environment.GetEnvironmentVariable("OPENAI_MODEL");
50+
51+
ulong channelId = defaultDiscordChannelId;
52+
if (!string.IsNullOrWhiteSpace(envDiscordChannelId) && ulong.TryParse(envDiscordChannelId, out ulong parsed))
53+
{
54+
channelId = parsed;
55+
}
56+
57+
DiscordClientConfig discordConfig = new()
58+
{
59+
BotToken = string.IsNullOrWhiteSpace(envDiscordToken) ? defaultDiscordToken : envDiscordToken,
60+
ChannelId = channelId
61+
};
62+
63+
OpenAIClientConfig openAiConfig = new()
64+
{
65+
ApiKey = string.IsNullOrWhiteSpace(envOpenAiApiKey) ? defaultOpenAiApiKey : envOpenAiApiKey,
66+
Model = string.IsNullOrWhiteSpace(envOpenAiModel) ? defaultOpenAiModel : envOpenAiModel
67+
};
68+
```
69+
70+
### 2) Wire up Discord + OpenAI and run
71+
72+
- From repo root: `dotnet run --project src/samples/01.DiscordAgent -c Release`
73+
- The app starts Discord and OpenAI daemons, then bridges chat↔agent in the configured channel. Type in the channel; the bot replies there.
74+
75+
Minimal wiring from Sample 01 (`Program.cs`):
76+
77+
```csharp
78+
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
79+
builder.Services.AddLogging(b => b.AddConsole());
80+
builder.Services.AddDiscordChat(discordConfig);
81+
builder.Services.AddOpenAIAgents(openAiConfig, registration =>
82+
{
83+
registration.EnableStreaming();
84+
});
85+
86+
// Optional: override OpenAI mapping with templating
87+
builder.Services.AddScoped<ITransmuter<OpenAIEntry, ResponseItem?>, DiscordOpenAITemplatingTransmuter>();
88+
89+
// Route chat ↔ agent via a simple MagikBlock
90+
builder.Services.BuildCoven(c => c.MagikBlock<Empty, Empty, RouterBlock>().Done());
91+
92+
IHost host = builder.Build();
93+
ICoven coven = host.Services.GetRequiredService<ICoven>();
94+
await coven.Ritual<Empty, Empty>(new Empty());
95+
```
96+
97+
Router logic (Sample 01 `RouterBlock.cs`):
98+
99+
```csharp
100+
await foreach ((long _, ChatEntry? entry) in _chat.TailAsync(0, cancellationToken))
101+
{
102+
if (entry is ChatAfferent inc)
103+
{
104+
await _agents.WriteAsync(new AgentPrompt(inc.Sender, inc.Text), cancellationToken);
105+
}
106+
}
107+
108+
await foreach ((long _, AgentEntry? entry) in _agents.TailAsync(0, cancellationToken))
109+
{
110+
switch (entry)
111+
{
112+
case AgentResponse r:
113+
await _chat.WriteAsync(new ChatEfferentDraft("BOT", r.Text), cancellationToken);
114+
break;
115+
case AgentThought t:
116+
// optionally surface thoughts to chat
117+
break;
118+
}
119+
}
120+
```
121+
122+
### Troubleshooting
123+
124+
- Discord: If no messages appear, verify the bot has access to the channel, Message Content Intent is enabled, and `ChannelId` is correct.
125+
- OpenAI: If errors occur on first response, confirm the API key and model name are valid for your account.
126+
- Networking: Corporate proxies/firewalls can block Discord/OpenAI APIs; ensure outbound HTTPS is allowed.
127+
128+
### Extensibility
129+
130+
Window policies: tune output chunking/summarization. Example (from Sample 01 `Program.cs`):
131+
132+
```csharp
133+
// Paragraph-first + tighter max-length for agent outputs
134+
builder.Services.AddScoped<IWindowPolicy<AgentAfferentChunk>>(_ =>
135+
new CompositeWindowPolicy<AgentAfferentChunk>(
136+
new AgentParagraphWindowPolicy(),
137+
new AgentMaxLengthWindowPolicy(1024)));
138+
139+
// Optionally tune thought chunking independently
140+
// builder.Services.AddScoped<IWindowPolicy<AgentAfferentThoughtChunk>>(_ =>
141+
// new CompositeWindowPolicy<AgentAfferentThoughtChunk>(
142+
// new AgentThoughtSummaryMarkerWindowPolicy(),
143+
// new AgentThoughtMaxLengthWindowPolicy(2048)));
144+
```
145+
146+
Custom OpenAI templating: override prompt/response item mapping to inject context (from `DiscordOpenAITemplatingTransmuter.cs`):
147+
148+
```csharp
149+
internal sealed class DiscordOpenAITemplatingTransmuter : ITransmuter<OpenAIEntry, ResponseItem?>
150+
{
151+
public Task<ResponseItem?> Transmute(OpenAIEntry Input, CancellationToken cancellationToken = default)
152+
{
153+
return Input switch
154+
{
155+
OpenAIEfferent u => Task.FromResult<ResponseItem?>(
156+
ResponseItem.CreateUserMessageItem($"[discord username:{u.Sender}] {u.Text}")),
157+
OpenAIAfferent a => Task.FromResult<ResponseItem?>(
158+
ResponseItem.CreateAssistantMessageItem($"[assistant:{a.Model}] {a.Text}")),
159+
_ => Task.FromResult<ResponseItem?>(null)
160+
};
161+
}
162+
}
163+
```
164+
165+
Surface agent thoughts: optionally echo internal thinking to the chat (from `RouterBlock.cs`):
166+
167+
```csharp
168+
case AgentThought t:
169+
// Uncomment to stream thoughts to the channel
170+
// await _chat.WriteAsync(new ChatEfferentDraft("BOT", t.Text), cancellationToken);
171+
break;
172+
```
173+
### I don't want to make a discord bot.
174+
Don't use Discord? No problem. One line change to swap to using Console as your chat of choice.
175+
```csharp
176+
// Replace
177+
builder.Services.AddDiscordChat(discordConfig);
178+
// with
179+
builder.Services.AddConsoleChat(new ConsoleClientConfig
180+
{
181+
InputSender = "console",
182+
OutputSender = "BOT"
183+
});
184+
185+
// Keep OpenAI registration as-is
186+
builder.Services.AddOpenAIAgents(openAiConfig);
187+
```
188+
189+
### I want to configure my model to do different things
190+
You can use any settings available on the OpenAIClientConfig. For example, you could make the model chew longer by setting Effort = ReasoningEffort.High
191+
192+
```csharp
193+
OpenAIClientConfig openAiConfig = new()
194+
{
195+
ApiKey = "<your-openai-api-key>",
196+
Model = "gpt-5-2025-08-07",
197+
Reasoning = new ReasoningConfig { Effort = ReasoningEffort.High }
198+
};
199+
200+
// Then register
201+
builder.Services.AddOpenAIAgents(openAiConfig);
202+
```
203+
204+
## Overview
205+
Ever felt like it was too hard to get products that you pay for to talk to each other? Perhaps felt like they should just work together... magically? :P
206+
207+
You are in the right place.
208+
209+
### Structure
210+
Every Coven is organized into a "spine" of MagikBlocks, executing one after the other.
211+
Each MagikBlock execution represents a unique scope with a fixed input and output type.
212+
> _Cheatcodes_: Use Empty as an input if you want to route to a MagikBlock with no inputs.
213+
214+
By starting Daemons and reading journals, your block executes the logic it needs, abstracted from the downstream implementation. The layers that define these abstractions are the "branches" that stretch off of your MagikBlock's execution. Coven offers two convenient abstractions:
215+
- **Coven.Chat**: Multi-user conversations.
216+
- **Coven.Agents**: Working with an AI powered Agent to complete your goals.
217+
218+
Built on the other side of the "branch" abstractions are Coven's handcrafted integrations with external systems. These integrations are like the "leaves" of our twisted tree, they translate Coven standard abstractions to an external system.
219+
- **Coven.Chat.Discord**: Use discord to chat with your Coven.
220+
- **Coven.Chat.Console**: Use a terminal to chat with your Coven.
221+
- **Coven.Agents.OpenAI**: Send requests to an agent from your Coven.
222+
223+
### Why use Coven?
224+
Anyone can write new branches or leaves and they will seamlessly integrate with your software.
225+
226+
Alternatively, because we are the easiest way to get agents to collaborate with users and each other.
227+
228+
### Vocabulary Cheatsheet
229+
> Core
230+
- MagikBlock: a unit of work with `DoMagik` that reads/writes journals.
231+
- Daemon (`ContractDaemon`): long‑running background service started by a block.
232+
- Scrivener (`IScrivener<T>`): append-only journal for typed entries; supports tailing.
233+
- Transmuter: pure mapping between types; `IBiDirectionalTransmuter` supports both directions.
234+
- Ritual: an invocation that executes a pipeline of MagikBlocks.
235+
- Entry: a record written to a journal (e.g., `ChatEntry`, `AgentEntry`).
236+
237+
> Streaming and Window/Shatter
238+
- Window Policy: rules that group stream chunks into windows for emission.
239+
- Shatter Policy: rules that split entries into smaller chunks for windowing.
240+
- Chunk: stream fragment (e.g., `AgentAfferentChunk`, `AgentAfferentThoughtChunk`).
241+
- Batch Transmuter: combines a window of chunks into an output (response or thought).
242+
243+
> Structure
244+
- Leaf: Connects your currently executing block to an external system. Lives at the end of a branch.
245+
- Branch: Services that connect your currently executing block to an external system via an abstraction. For example, Coven.Agents and Coven.Chat
246+
- Spine: Your executing ritual. Each vertebrae is a MagikBlock in your ritual.
247+
- Afferent/Efferent: The direction that a message is traveling.
248+
- Efferent: from spine to leaf.
249+
- Afferent: from leaf to spine.
250+
251+
## Licensing
252+
253+
**Dual‑license (BUSL‑1.1 + Commercial):**
254+
255+
* **Community**: Business Source License 1.1 (BUSL‑1.1) with an Additional Use Grant permitting Production Use if you and your affiliates made **< US $100M** in combined gross revenue in the prior fiscal year. See `LICENSE`.
256+
* **Commercial/Enterprise**: available under a separate agreement. See `COMMERCIAL-TERMS.md`.
257+
258+
*Change Date/License*: `LICENSE` specifies a Change License of **MIT** on **2029‑09‑11**.
259+
260+
## Support
261+
262+
* Patreon: [https://www.patreon.com/c/Goldenwitch](https://www.patreon.com/c/Goldenwitch)
263+
264+
> © 2025 Autumn Wyborny. BUSL 1.1, free for non-profits, individuals, and commercial business under 100m annual revenue.

0 commit comments

Comments
 (0)