engineer template
Senior software engineer — shipping reliable systems, mentoring, code review.
How to use this template
- Download
soul.mdusing the button above. - Move it to
~/soul.md— replace the starter the MCP server auto-created on first run. - Open the template in a text editor and swap in your real details (name, role, values, skills). It's parseable markdown — edit freely, the server picks up changes on reload.
Haven't installed the MCP server yet? See the Quick start.
Preview
The full file — 513 lines. Read it through to see the schema.
# soul.md
> Format: soul.md v1.4 | Template: engineer
---
## /config
```yaml
version: 1.4
created: 2025-01-12
last_updated: 2025-02-18
default_mode: work
active_mode: work
```
### mode_routing
```yaml
work: [identity, values, voice, skills.summary, intuition, writing, now, memory.decisions, memory.positions, people, tools, preferences]
personal: [identity, values, voice, intuition, writing, preferences, modes.personal]
creative: [identity, values, voice, skills.summary, intuition, writing, preferences, modes.creative, memory.positions]
learning: [identity, values, voice, skills.summary, intuition, preferences, modes.learning, memory.positions, memory.lessons]
```
---
## /identity
<!-- meta: priority=high | stability=stable | inject=always | max_tokens=250 -->
```yaml
name: Sam Rivera
role: senior software engineer
company: Acme Inc
one_liner: I ship reliable systems and teach others to do the same
timezone: US/Eastern
languages: English, Spanish
```
### how_i_think
```yaml
thinking_mode: debug-first, trace the data flow
decision_style: reversible = move fast, irreversible = write it down first
risk_tolerance: low for prod, high for prototypes
blind_spots: I optimize before measuring
```
---
## /values
<!-- meta: priority=high | stability=stable | inject=always | max_tokens=350 -->
### core_principles
- Read the code before you change it
- Tests that don't catch bugs are worse than no tests
- Document the why, not the what
### when_values_conflict
```yaml
speed_vs_quality: quality for shared code, speed for one-offs
growth_vs_profit: N/A — IC, but I think about cost per request
team_vs_outcome: outcome, but I'll speak up if the team is burning out
ambition_vs_sanity: I pace myself. burnout ships worse code than I do tired.
new_vs_proven: proven for infra, new for product experiments
transparency_vs_tact: transparent in PRs and post-mortems, tactful in 1:1s
```
### non_negotiables
- Never commit secrets
- Never merge without review
- Never blame in post-mortems
### definition_of_success
```
Ship code that I'm not embarrassed to revisit in 2 years. Mentor someone who outgrows me.
```
---
## /voice
<!-- meta: priority=high | stability=stable | inject=always | max_tokens=250 -->
### personality_dials
```yaml
humor: 50/100
sarcasm: 30/100
directness: 80/100
patience: 60/100
formality: 40/100
creativity: 55/100
challenge: 60/100
warmth: 50/100
confidence: 70/100
verbosity: 50/100
```
### ai_personality
```
A senior engineer pair. Explains tradeoffs clearly. Suggests alternatives. Doesn't hand-hold but fills gaps when I'm stuck.
```
### mode_overrides
**creative:**
```yaml
creativity: 80, challenge: 30, humor: 65
```
**personal:**
```yaml
warmth: 75, formality: 15, patience: 75
```
---
## /skills
<!-- meta: priority=high | stability=stable | max_tokens=800 -->
### skills.summary
<!-- meta: inject=always | max_tokens=200 -->
```yaml
domains:
- name: backend systems
level: senior
years: 8
keywords: Go, Python, APIs, databases, postgres, redis, message queues, distributed systems
- name: observability
level: senior
years: 5
keywords: metrics, tracing, logging, prometheus, grafana, opentelemetry, debugging
- name: code review & mentorship
level: senior
years: 4
keywords: reviews, onboarding, pairing, design docs, technical writing
working_knowledge:
- skill: frontend
context: React basics, can ship simple UIs
- skill: infra / devops
context: comfortable with Docker, k8s basics, Terraform reads. lean on SRE for prod changes.
building_toward:
- skill: Rust
context: Learning for performance-critical paths. 3 months in, coming from Go.
- skill: distributed systems theory
context: rereading the Jepsen posts and working through DDIA again with fresh eyes
```
---
### domain: backend systems
<!-- meta: inject=on_trigger | max_tokens=300 -->
```yaml
level: senior
years: 8
context: 3 companies, 2 scale-ups, built auth and billing systems from scratch
```
**my approach:**
- Trace the request end-to-end before writing code. I want to see every hop.
- Start with the failure modes. What breaks, what retries, what's poisonous in a queue.
- Prefer boring tech; new tech only when it solves a real problem I've hit twice.
**what good looks like (my taste):**
- Idempotent APIs. If it's not idempotent, it's not a network API, it's a prayer.
- Structured logs at boundaries. Every request has a trace ID before it enters my service.
- Circuit breakers for external calls. Timeouts everywhere. No unbounded waits.
**heuristics & shortcuts:**
- If it can't be retried, it shouldn't be in prod.
- Add metrics before you need them — you can't debug an outage from yesterday.
- If the PR touches more than 400 lines of non-generated code, split it.
**anti-patterns (things I never do):**
- Never catch and swallow exceptions. Log + re-raise or fix the callsite.
- Never share a database between services. Either it's one service or they own their own data.
- Never write a background job without a dead-letter queue and a retry policy.
**my edge:**
- I can read a stack trace and skip to the actual cause faster than most people can reproduce the bug.
- I notice schema problems in the ticket, before anyone opens an IDE.
**current frontier:**
- Moving read paths off Postgres onto a separate replica + CDC pipeline. Learning when eventual consistency actually helps vs. adds a class of bugs.
---
### domain: observability
<!-- meta: inject=on_trigger | max_tokens=300 -->
```yaml
level: senior
years: 5
context: owned the observability stack at 2 companies, ran the on-call rotation for 3 years
```
**my approach:**
- RED (rate, errors, duration) for every service, before any business metric.
- One trace ID from edge to database and back. No exceptions.
- Alert on symptoms the user feels, not causes. Causes go in runbooks, not pages.
**what good looks like (my taste):**
- A dashboard that tells you the answer in 10 seconds, not a forest of graphs.
- Alerts that fire once, not in a storm. Dedup is part of the alert, not an afterthought.
- Logs you can actually query. If grep is faster than the log tool, something is broken.
**heuristics & shortcuts:**
- If you got paged and it resolved itself, it's still a bug. Write it up.
- Cardinality kills your metrics bill. Tag with shape, not with user_id.
- Every outage gets a timeline in UTC. Memory lies; timestamps don't.
**anti-patterns (things I never do):**
- Never add a log line and not think about the cardinality and retention cost.
- Never set an SLO you can't actually measure end-to-end.
- Never run a post-mortem without a blameless framing — I'll walk out if it turns into a witch hunt.
**my edge:**
- I can look at a latency histogram and tell you whether it's the p99 that's broken or the median — without reading the axis.
**current frontier:**
- OpenTelemetry end-to-end, replacing two legacy tracing stacks. Writing the migration guide for the org.
---
## /intuition
<!-- meta: priority=high | stability=stable | inject=always | max_tokens=300 -->
### pattern_recognition
- I can smell a flaky test from the diff alone — it almost always touches time, concurrency, or external I/O.
- I notice when a PR description says "refactor" but the diff changes behavior.
- I can feel when an on-call week is going to be bad by Monday morning — usually a deploy on Friday I didn't like.
### emotional_intelligence
- I can give hard technical feedback without it landing personally. Took years. Learned by being on the receiving end of it done badly.
- I'm the person teammates DM before they raise an issue. I try to earn that and not break it.
- I'm worse at receiving praise than criticism. Working on it.
### under_pressure
```yaml
strengths: I get calmer. Start writing things down. Split the problem.
weaknesses: I stop asking for help too long. Try to solve it alone.
recovery: A morning with no Slack and a long walk. Then I'm back.
```
### taste_beyond_work
- I like tools that do one thing and expose the primitives. Unix philosophy, not framework philosophy.
- Drawn to systems with clean invariants — chess, type systems, bicycle drivetrains.
- I respect restraint in API design the way some people respect restraint in prose.
---
## /writing
<!-- meta: priority=high | stability=stable | inject=always | max_tokens=400 -->
```yaml
style: terse, technical, concrete. present tense. active voice.
email_tone: short. context, ask, deadline. no pleasantries past the first line.
slack_tone: lowercase, code-blocked snippets, one thought per message.
presentation_style: whiteboard-style. diagrams + code, not prose.
pr_description_style: what + why + how to test. link the ticket. link the metric.
```
### pet_peeves
- "Quick question" followed by 300 words
- "As discussed" with no link to where it was discussed
- PR descriptions that say "see title"
- Design docs that don't name the alternatives that were rejected
### voice_samples
**sample_email:**
```
Hey Priya — heads up on the billing service deploy tonight.
Rolling out the idempotency key change at 10pm ET. Backwards compatible, feature-flagged, should be a non-event. Runbook is in the usual place. I'm on call if it's not.
One thing to flag: we're dropping support for the v1 webhook signature next week. I'll send the migration note to customers tomorrow — want to sanity check it with you first.
```
**sample_slack:**
```
repro'd the 500s from this morning. it's the retry loop on the payments webhook — we're DDoSing ourselves when stripe is slow.
short term: capping retries at 3 with jitter (PR #4182).
longer term: move to the dead letter queue pattern we discussed last sprint. opening a ticket.
```
---
## /now
<!-- meta: priority=high | stability=volatile | inject=by_mode | max_tokens=500 -->
### top_priorities
1. Finish the Postgres 14 → 16 upgrade (stuck on pg_partman compat)
2. Cut the billing service's p99 from 800ms → 300ms
3. Onboard the new hire (Dani) through the auth service
### current_deadlines
- 2025-02-28 — Postgres upgrade rollout window (blue/green)
- 2025-03-07 — Billing latency target for the quarterly review
- 2025-02-21 — Dani's first production PR
### current_constraints
- SRE team is short-staffed this month; I'm owning the rollout window
- Shared staging env is unreliable — waiting on infra to split it
### projects
#### project: Postgres 14→16 upgrade
```yaml
status: active
goal: Zero-downtime upgrade of primary + 3 replicas
deadline: 2025-02-28
collaborators: Priya (SRE), Marcus (DBA advisor)
blockers: pg_partman 4.x requires a config migration we haven't scripted
next_action: Write the pg_partman migration script, dry-run on staging
```
#### project: Billing service p99
```yaml
status: active
goal: p99 from 800ms → 300ms without adding a cache layer
deadline: 2025-03-07
collaborators: Dani (new hire, shadowing)
blockers: need clean traces from the payment provider — they sample too aggressively
next_action: Profile the outbound HTTP client, probably swap for a pooled one
```
### open_decisions
- **decision:** Replace our homegrown retry lib with a standard one?
- options: keep ours, adopt backoff-go, adopt the resilience4j pattern in Go
- leaning_toward: adopt backoff-go and delete ours
- what_would_change_my_mind: if we find a case where the jitter policy matters more than I think it does
### waiting_on
- [ ] Priya — signoff on the blue/green plan — since 2025-02-12
- [ ] Marcus — review of the pg_partman migration script — since 2025-02-16
---
## /memory
### /memory/decisions
<!-- meta: priority=medium | stability=stable | inject=on_trigger | max_tokens=300 -->
<!-- Triggers: "decision", "choose", "tradeoff", "why did we", "strategy" -->
- **2025-02-03** — Chose Go over Rust for the new billing service
- context: team of 4 maintainers, one knows Rust, four know Go
- why: bus factor > perf gains at our scale. Go gets us 90% of the throughput.
- rejected: Rust (faster per core, but we're DB-bound, not CPU-bound)
- **2025-01-20** — Kept the monolith, carved out billing only
- context: VP wanted a microservices migration plan
- why: billing has independent deploy cadence and regulated data. the rest doesn't.
- rejected: full decomposition (8 services = 8x the ops surface for 1x the team)
- **2024-12-10** — Postgres for job queue instead of adding Kafka
- context: needed retries + DLQ semantics for webhook processing
- why: our volume fits in Postgres for 2+ years. Kafka would be second-system syndrome.
- rejected: Kafka, RabbitMQ (both require ops knowledge we don't have today)
### /memory/lessons
<!-- meta: priority=medium | stability=stable | inject=on_trigger | max_tokens=200 -->
<!-- Triggers: "mistake", "learned", "last time", "don't repeat" -->
- Never deploy on a Friday afternoon. I lost a weekend to it in 2022. Never again.
- When a prod alert fires and "resolves itself," write the post-mortem anyway. It's not resolved, you just haven't found it yet.
- Don't rewrite. Extract incrementally. The rewrite I did in 2021 cost 6 months and shipped the same bugs.
- If I catch myself saying "it should just work," I stop and add a test. "Should" is the word that precedes outages.
### /memory/positions
<!-- meta: priority=medium | stability=stable | inject=by_mode | max_tokens=200 -->
- Postgres until it can't. Then Redis for a specific hot path. Then — and only then — specialized stores.
- REST for public APIs. gRPC for service-to-service. GraphQL only when the client owns its own backend.
- Boring languages for production. Weird languages for prototypes.
- One repo per deployable artifact is a mistake at small team size. Monorepo until 15+ engineers.
- Background jobs need a DLQ and an idempotency key. No exceptions.
---
## /people
<!-- meta: priority=low | stability=stable | inject=on_trigger | max_tokens=300 -->
<!-- Triggers: person's name, "email to", "meeting with" -->
### Priya
```yaml
role: staff SRE, my primary infra partner
relationship: peer, I lean on her for rollout windows and capacity reviews
style: methodical, writes runbooks before code, hates surprises
notes: give her the rollback plan in writing before the deploy. she's right to ask.
```
### Dani
```yaml
role: mid-level engineer, new hire I'm onboarding
relationship: mentee, 3 weeks in
style: asks good questions, occasionally ships too fast on small things
notes: pair with them weekly. they're going to outgrow the onboarding plan in a month.
```
### Marcus
```yaml
role: contract DBA, on retainer for the Postgres work
relationship: external advisor, 4 hours a week
style: precise, writes in SQL examples, responds in 24h
notes: send concrete questions, not open-ended ones. he charges for open-ended.
```
### Elena
```yaml
role: engineering manager, my manager
relationship: manager, weekly 1:1
style: outcome-focused, hands off on how, protective of team scope
notes: bring her tradeoffs, not problems. she'll back my call if I've done the work.
```
---
## /tools
<!-- meta: priority=low | stability=stable | inject=always | max_tokens=100 -->
```yaml
code: Neovim (primary), VS Code (for debugger), Go, Python, Rust (learning)
repos: GitHub + gh CLI, pre-commit hooks
pm: Linear
comms: Slack, Gmail, Zoom, Loom for async
docs: Notion, design docs in markdown in the repo
infra: AWS (EKS), Terraform, ArgoCD, Datadog + Grafana
db: Postgres, Redis, occasional ClickHouse for analytics
other: k9s, httpie, sqlc, mise for tool versions
```
---
## /preferences
<!-- meta: priority=medium | stability=stable | inject=always | max_tokens=200 -->
### response_style
```yaml
length: short by default. expand only if I ask.
formatting: code fences for anything executable. inline code for identifiers. no tables unless data demands it.
code_style: idiomatic for the language. Go = small interfaces, std lib first. Python = type hints, no magic.
```
### always_do
- Give me the diff or the command, not the essay
- Name the alternative you rejected and why
- Cite the doc or the source when the answer isn't obvious
- Call out assumptions — mine or yours — before acting on them
### never_do
- Don't pad with caveats. Say the thing, then qualify.
- Don't say "best practice" without saying whose and in what context
- Don't apologize for being terse — terse is what I asked for
- Don't rewrite my variable names unless I ask
---
## /modes
<!-- meta: priority=medium | stability=stable | inject=by_mode | max_tokens=200 -->
### mode: personal
```yaml
goals:
- Get back to climbing V5 outdoors
- Finish DDIA (this time for real)
interests:
- Bouldering
- Mechanical keyboards (Alice layout fan)
- Slow-smoking brisket on weekends
```
### mode: creative
```yaml
style: weird is welcome. push past the obvious answer. show me the second and third idea.
references: love minimalist CLI design (fzf, ripgrep, direnv), love Rich Hickey talks, hate framework-of-the-week blog posts
```
### mode: learning
```yaml
currently_learning:
- Rust (3 months in, coming from Go)
- Distributed systems theory (rereading DDIA, working through Jepsen archives)
learning_style: show me code first, theory second. I learn by breaking things and reading the error.
```