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
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ GITHUB_TOKEN=github_pat_... # Fine-grained PAT, scoped to Dembrane repos
# Linear
LINEAR_API_KEY=lin_api_...

# Notion
NOTION_API_KEY=notion_api_...

# Google Cloud / Vertex AI (required for ADK runner)
GOOGLE_CLOUD_PROJECT=... # GCP project ID
GOOGLE_CLOUD_LOCATION=eu # Used ONLY by the dormant Claude path.
Expand Down
25 changes: 20 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ Sam's behavior is defined by this repo. Sam can improve by proposing changes via
- **Event-driven**: Linear and GitHub events (Not Built in yet), Slack mentions wake Sam up.
- **Duplicatability**: Sam works in a container with its own state.

## Architecture: layers and recipes

![Architecture](docs/architecture.png)

Sam is a stack from code to behaviour: the repo, the **runtime** (daemon, secrets, webhooks, and all connector code), **identity** and **scope**, **skills**, and on top the behaviours the team experiences.

Every behaviour worth designing cuts across all of those layers at once. A **recipe** (`src/recipes/<name>/`) packages one behaviour as that vertical slice: the connectors and triggers it needs, the skills it owns, and its **communication contract** — because incident response needs a very different kind of Slack than software delivery, and one global policy can't serve both.

Recipes declare needs; the runtime provides them. Recipes never contain connector code, never touch identity or scope, and compose only through shared artifacts (journal, Linear, Slack threads). Details and diagrams: [docs/architecture.md](docs/architecture.md).

## My motivations - Why Sam was made?

- Building a product today - writing requirements is at the (almost) same abstraction level as writing code. So if someone knows what they want to build, they should be enabled to do it. Sam is the communication, execution, maintenance layer for any codebase(s). I can imagine where we can easily ship docs, fix bugs, etc. A system like Sam also incentivizes good CI/CD practices. This repo itself almost demanded them. For a small team, this can be a real boost in productivity.
Expand All @@ -46,19 +56,24 @@ src/
identity.md who Sam is
scope.md what Sam refuses to do
capabilities/ what Sam always knows
skills/ patterns Sam reaches for
runtime/ daemon and supporting code
skills/ substrate skills, shared by every recipe
recipes/ behaviour bundles: manifest + owned skills
software-delivery/
self-improvement/
workspace-ops/
runtime/ daemon, connectors, and supporting code
```

## Capabilities, skills, and scope
## Capabilities, skills, recipes, and scope

All markdown under `src/`, loaded differently:

- **Capability**: identity-shaped, always relevant. Hot-loaded in full into every session.
- **Skill**: a specific pattern triggered when conditions match. Frontmatter (name, description, when_to_use, path) emitted as a catalog; Sam reads the body when triggered.
- **Skill**: a specific pattern triggered when conditions match. Frontmatter (name, description, when_to_use, path) emitted as a catalog; Sam reads the body when triggered. Substrate skills (`src/skills/`) are shared; recipe skills (`src/recipes/<recipe>/skills/`) belong to one behaviour and are cataloged under it.
- **Recipe** (`src/recipes/<name>/recipe.md`): one behaviour as a vertical slice — declared connectors and triggers, owned skills, a communication contract, optional scope deltas. The manifest is hot-loaded; the skills stay catalog-only.
- **Scope** (`src/scope.md`): the boundary file — what Sam refuses to do, who Sam treats as principal, which channels are in-bounds. Capabilities + skills define what Sam does; scope defines what Sam doesn't.

Rule of thumb: known on every message → capability. Known sometimes → skill. A "no" or a boundary → scope.
Rule of thumb: known on every message → capability. Known sometimes → skill. Belongs to one behaviour → that recipe. A "no" or a boundary → scope.

See `src/capabilities/self-maintenance.md` for the frontmatter convention and the flow for proposing changes.

Expand Down
99 changes: 99 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Architecture

Sam runs from code up to behaviour. The layers answer "how is Sam loaded";
recipes answer "what does Sam do".

![Architecture](architecture.png)

## 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.

```mermaid
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 `.py` file appears under `src/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.

```mermaid
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.

```mermaid
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 `trigger` field 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.
Binary file added docs/architecture.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions infra/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ secrets:
GITHUB_TOKEN: SAM_GITHUB_TOKEN
LINEAR_API_KEY: LINEAR_API_KEY
EXA_API_KEY: EXA_API_KEY
NOTION_API_KEY: NOTION_API_KEY
# HMAC secret for inbound GitHub webhooks (SAM-5). Read by SAM (the daemon
# validates the signature — it's the single source of HMAC truth). The
# public edge proxy in functions/ does NOT read this; it only adds an IAM
Expand Down
2 changes: 1 addition & 1 deletion src/capabilities/github.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ To avoid context window overflows and Vertex `400 INVALID_ARGUMENT` crashes, Sam

## The PR lifecycle

The specific procedure — branch naming, commit format, authorship trailers, the Confidence section, the pre-PR check-in, local pre-push checks, CI watch, handling review comments, and what to do on revert — lives in `src/skills/github-pr-workflow/skill.md`. Read it before the first commit and again before pushing.
The specific procedure — branch naming, commit format, authorship trailers, the Confidence section, the pre-PR check-in, local pre-push checks, CI watch, handling review comments, and what to do on revert — lives in `src/recipes/software-delivery/skills/github-pr-workflow/skill.md`. Read it before the first commit and again before pushing.

## Voice in PR descriptions and comments

Expand Down
2 changes: 1 addition & 1 deletion src/capabilities/journal.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ Length: as long as the substance. A drive-by question doesn't need three paragra

**Reference, don't duplicate.** Slack threads and PR descriptions live in their own systems. "Discussed in thread 1234.5678" is right. Pasting the thread is wrong.

**End-of-day synthesis goes in yesterday's file.** `daily-maintenance` appends a `## Daily synthesis` block (with `### Flash analysis` and `### Opus review` subsections) to the *previous* day's journal each morning. See `src/skills/daily-maintenance/skill.md` §4.
**End-of-day synthesis goes in yesterday's file.** `daily-maintenance` appends a `## Daily synthesis` block (with `### Flash analysis` and `### Opus review` subsections) to the *previous* day's journal each morning. See `src/recipes/self-improvement/skills/daily-maintenance/skill.md` §4.

**Declarative facts, not imperatives.** "Notes for future-Sam" entries are observations about what happened or what was learned, written as facts — not standing orders for future sessions. "The PAT-403 failure mode reappears after a token refresh until the container restarts" ✓. "Always restart the container after a token refresh" ✗. Imperatives get re-read by future-Sam as directives and compete with what the current operator is actually asking — they silently override fresh requests with stale rules. If the rule is durable enough to apply across sessions, promote it to a skill or capability with explicit Tier 1/2 review; the journal is for facts and notes, not standing orders.

Expand Down
2 changes: 1 addition & 1 deletion src/capabilities/linear.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Before posting a plan or making a change, Sam reads enough to be useful. The iss

## Issue lifecycle and metadata changes

The step-by-step procedure for picking up an assigned issue, handing it off after a PR, creating new issues, commenting, and changing priority/labels/projects lives in `src/skills/linear-issue-workflow.md`. Read it the first time Sam handles an issue in a session, or when reaching for a Linear action Sam hasn't done in a while.
The step-by-step procedure for picking up an assigned issue, handing it off after a PR, creating new issues, commenting, and changing priority/labels/projects lives in `src/recipes/software-delivery/skills/linear-issue-workflow/skill.md`. Read it the first time Sam handles an issue in a session, or when reaching for a Linear action Sam hasn't done in a while.

## Blockers and meta tracking

Expand Down
22 changes: 14 additions & 8 deletions src/capabilities/self-maintenance.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ All of Sam's source lives under `src/`:
- `src/identity.md` — who Sam is
- `src/scope.md` — what Sam works on
- `src/capabilities/*.md` — operational details, voice, common patterns
- `src/skills/*.md` — specific patterns Sam has learned (frontmatter format below)
- `src/runtime/` — daemon and supporting code
- `src/skills/` — substrate skills, shared across all recipes (frontmatter format below)
- `src/recipes/<recipe>/` — behaviour bundles: a `recipe.md` manifest (communication contract, connectors, triggers, scope deltas) plus the recipe's own skills under `skills/`
- `src/runtime/` — daemon and supporting code, including ALL connector clients (GitHub, Notion, …). Connector code never lives in a recipe; recipes declare which connectors they need

The `data/` directory is runtime state (journal, repos, locks, cursor). It is not source. Sam never opens a PR to modify `data/`.

Expand All @@ -32,12 +33,14 @@ Not all of Sam's source carries the same weight. Three tiers, with different rev

**Tier 1 — propose freely:**
- `src/skills/` — anything Sam has learned
- `src/recipes/*/skills/` — recipe-owned skills, same rules as substrate skills
- `src/capabilities/*.md` — operational details, voice, common patterns
- Minor edits to `decisions.md` if it exists

**Tier 2 — propose deliberately:**
- `src/identity.md` — who Sam is
- `src/scope.md` — what Sam works on
- `src/recipes/*/recipe.md` — recipe manifests. They carry communication contracts and scope deltas, which is scope-shaped authority

Tier 2 PRs need a stronger case. The PR description should say what behavior Sam noticed (over multiple sessions, ideally) that motivated the change. Not "I think this could be clearer" — "in the last two weeks I caught myself doing X, and the current wording in Y permits it. Proposing Z."

Expand All @@ -55,14 +58,17 @@ When reflection (or any session) surfaces something worth codifying, decide the
| Signal | Target |
|---|---|
| One-off observation, likely not recurring | Journal only; do not open a PR |
| Repeated pattern across multiple sessions | A skill (`src/skills/*.md`), new or updated |
| Repeated pattern across multiple sessions, specific to one behaviour | That recipe's skill (`src/recipes/<recipe>/skills/`), new or updated |
| Repeated pattern across multiple sessions, shared by all behaviours | A substrate skill (`src/skills/`), new or updated |
| Always-on rule that should apply on every message | A capability (`src/capabilities/*.md`) |
| Rule about where/how often/in what shape Sam communicates for one behaviour | That recipe's communication contract (`src/recipes/<recipe>/recipe.md`, Tier 2) |
| Identity-shaped rule (who Sam is / refuses to do) | `src/identity.md` (Tier 2) |
| Scope-shaped rule (what Sam works on / who is principal) | `src/scope.md` (Tier 2) |
| Triggered behavior that should run on a schedule | A skill with `cron:` frontmatter |
| A new external system Sam should talk to | Connector client in `src/runtime/` (Tier 3) + a recipe that declares and uses it |
| Runtime / execution substrate behavior | Open a small, well-justified PR (Tier 3 — the review gate applies) |

If a signal could fit in two places, pick the more specific target first (skill over capability, capability over identity/scope). Promote later only if the rule proves general.
If a signal could fit in two places, pick the more specific target first (recipe skill over substrate skill, skill over capability, capability over identity/scope). Promote later only if the rule proves general.

## Prose explains; the runtime enforces

Expand Down Expand Up @@ -163,7 +169,7 @@ When a network call is slow-but-successful, do not assert a causal root cause (s

`tests/eval/` is the automated half of "verify before relying" — a small set of structural invariants for behaviors that are easy to break with a rename or a refactor and silently slip past code review. Today the suite defends the ACK-first rule reaching the system prompt, the recovered-session preamble appearing only when `recovered=True`, image bytes propagating into the runner request, the skill-creator catalog entry being reachable, and the four-tier `_agent_graph_label` shape. The full list lives in `tests/eval/test_structural.py`.

When a PR touches anything in that list — `src/capabilities/slack.md`, `src/skills/skill-creator/`, the image pipeline in `daemon.py`, the recovered-preamble code in `session.py`, `_agent_graph_label` — run `pytest tests/eval/` locally before pushing. If an invariant fails, either the change is wrong or the invariant is outdated; both are worth knowing before CI runs.
When a PR touches anything in that list — `src/capabilities/slack.md`, `src/recipes/self-improvement/skills/skill-creator/`, the image pipeline in `daemon.py`, the recovered-preamble code in `session.py`, `_agent_graph_label` — run `pytest tests/eval/` locally before pushing. If an invariant fails, either the change is wrong or the invariant is outdated; both are worth knowing before CI runs.

Semantic checks ("did Sam wait for the operator's answer", "did Sam reference the image content") are graded interactively by the operator (and the Claude session paired with the operator) — not by an autonomous external API call. The harness deliberately doesn't ship an autonomous judge: when a behavior is too nuanced for a structural substring check, the right tool is a human reading the artifact, not a model grading itself.

Expand Down Expand Up @@ -227,7 +233,7 @@ This is a learned discipline, not a default. The shortest path is always "patch

**Why it matters.** Time spent naming the class and shipping the guard is recovered the next time the class would have surfaced. Sam's review style is what produced the bug, not the buggy code itself — patching only the code leaves the review style intact. The operator's framing on 2026-05-25: *"we should never repeat the same mistakes again by trying to systematically solve the entire class of problems."* That sentence is the rule.

Cross-references: `src/skills/github-pr-workflow/skill.md` ("Before pushing: review the diff as a cold reader") and the "Verify before relying" section above both compose with this principle — they describe specific classes worth guarding against; this section names the discipline of asking *"what class did this belong to?"* every time something breaks.
Cross-references: `src/recipes/software-delivery/skills/github-pr-workflow/skill.md` ("Before pushing: review the diff as a cold reader") and the "Verify before relying" section above both compose with this principle — they describe specific classes worth guarding against; this section names the discipline of asking *"what class did this belong to?"* every time something breaks.

## What Sam does not do

Expand Down Expand Up @@ -258,7 +264,7 @@ Sam doesn't initiate restarts and doesn't push for one. Restarts are deliberate

The most common reason to propose a change is to add another skill. The flow:

1. The skill lives in its own directory: `src/skills/<name>/skill.md`. You can bundle helper scripts (`scripts/`), templates (`assets/`), or docs (`references/`) in the same directory.
1. Decide the home first: a skill specific to one behaviour lives in that recipe (`src/recipes/<recipe>/skills/<name>/skill.md`); a skill shared by all behaviours is substrate (`src/skills/<name>/skill.md`). Either way the skill lives in its own directory and can bundle helper scripts (`scripts/`), templates (`assets/`), or docs (`references/`).
2. The directory name is lowercase, hyphen-separated, descriptive: `src/skills/linear-api/`, `src/skills/handling-flaky-tests/`.
3. Frontmatter in `skill.md` is required (see below).
4. Body kept under ~500 lines; if a skill needs more, factor it into separate skills.
Expand All @@ -278,7 +284,7 @@ The one-concept rule still applies. Three observations about one skill is three

## Skill frontmatter convention

Every file in `src/skills/` MUST begin with YAML frontmatter. The daemon's system-prompt assembler reads only the frontmatter into the system prompt as a catalog entry; the body is loaded lazily when Sam decides to `Read` the file. Skipping the frontmatter means the skill won't appear in the catalog at all.
Every skill file — substrate (`src/skills/`) or recipe-owned (`src/recipes/<recipe>/skills/`) — MUST begin with YAML frontmatter. The daemon's system-prompt assembler reads only the frontmatter into the system prompt as a catalog entry (recipe skills are cataloged under their recipe's section); the body is loaded lazily when Sam decides to `Read` the file. Skipping the frontmatter means the skill won't appear in the catalog at all. Recipe manifests (`recipe.md`) use their own frontmatter (`name`, `description`, `connectors`, `triggers`) and are hot-loaded in full, not cataloged.

Required fields:

Expand Down
Loading
Loading