Architecture
On this page
Sam runs from code up to behaviour. The layers answer “how is Sam loaded”; recipes answer “what does Sam do”.

Layers#
| layer | what lives there |
|---|---|
| runtime | daemon, cron, webhooks, secrets, storage, and every connector (GitHub, Notion, Slack, Linear). Deterministic code. Tier 3. |
| identity | who Sam is |
| scope | boundaries: principal, forbidden actions, working hours |
| skills | substrate skills in src/skills/, recipe-owned skills in src/recipes/*/skills/ |
| behaviour | what the team experiences: delivery, self-improvement, workspace ops |
Everything below identity is enforced by code. Everything above it is prompt.
src/capabilities/self-maintenance.md names the rule: prose explains, the
runtime enforces.
Recipes#
A behaviour is a vertical slice through those layers, not a file in one of
them. A recipe packages the slice: a recipe.md manifest (connectors,
triggers, communication contract, scope deltas) plus the skills it owns.
flowchart TB sd["software-delivery"] --> rt["runtime primitives"] si["self-improvement"] --> rt wo["workspace-ops"] --> rt
| recipe | communication contract | declares |
|---|---|---|
| software-delivery | thread-scoped, batched, quiet hours respected | github, linear, slack |
| self-improvement | daily broadcast to #sam, never urgent | github, slack |
| workspace-ops | confirmation-first, no PR/CI vocabulary | notion, slack |
The contract is the point. Different behaviours use Slack differently, and one
global policy can’t serve them all. Where a recipe is silent, the defaults in
src/scope.md hold.
Two rules keep the boundary clean:
- Connector code lives in the runtime, never in a recipe. A structural test
fails the build if a
.pyfile appears undersrc/recipes/. - Identity and scope sit outside recipes. A recipe can carry a bounded scope delta (Tier 2 review); it cannot touch who Sam is, who the principal is, or the “should not attempt” list.
Activation#
The daemon is recipe-blind today. Every session gets all manifests; Sam matches the work against them in context, the same way skills are selected.
sequenceDiagram participant Event participant Daemon participant Session Event->>Daemon: mention, cron fire, or webhook Daemon->>Session: prompt carrying every recipe manifest Session->>Session: pick the recipe, apply its contract
| trigger | how the recipe is determined |
|---|---|
| cron | routed in practice: the scheduled message names a skill path inside a recipe |
| webhook | routed in practice: the event playbook lives in software-delivery |
| mention | model judgment |
Planned: the daemon matches declared triggers and stamps the active recipe into the session preamble and the ledger. That makes the first two rows deterministic. The third stays model judgment; classifying free-form language is the model’s job.
Composition#
Recipes don’t call each other. They share artifacts: the journal, Linear, Slack threads, PRs. One recipe writes, another reacts.
flowchart LR
sd["software-delivery"] <--> a[("shared artifacts")]
si["self-improvement"] <--> a
wo["workspace-ops"] <--> a
Attribution#
- Journal entries tag
recipe: [<names>]in frontmatter. Self-reported, list-shaped, for reflection and rollups. Never for enforcement. - The ledger has no recipe field yet. The runtime only knows the recipe for
cron and webhook sessions, and the
triggerfield mislabels webhooks today. The stamp lands with declarative triggers. - Tool-call audit lines carry
session_id; recipe attribution is a join, not a per-line tag.