← Back to Blog
Building in Public

Following Your Own Patterns

christian crumlish•
A robot navigating by starlight, using self-established patterns rather than borrowed maps.
“Straight on till morning!”

Following Your Own Patterns

June 28, 2025

Here’s what happened when we went to add GitHub integration to Piper Morgan: it took about an hour, and most of that was just writing the tests.

Not because GitHub integration is simple (though their API is quite nice). Not because we cut corners or built something half-baked. But because we’d finally reached that magical state where your architecture makes new features feel inevitable rather than difficult.

This is the story of what it looks like when you start following your own patterns instead of fighting them.

Building on clean foundations

Coming off our technical debt cleanup from a couple days earlier — where we’d consolidated two orchestration systems into one and actually wrote tests for the system we were using — I was ready to tackle GitHub integration.

The plan was straightforward: enable users to say “create a ticket for this bug” and have Piper automatically create a properly formatted GitHub issue in the right repository with all the relevant context.

This was something the prototype was doing on, like, it’s second day, so I was pretty excited to be getting this MVP feature working in “the real thing.”

What I didn’t expect was how obvious the implementation path would be once we started following the patterns we’d already established.

Architecture that teaches you how to use it

Good architecture is like a well-designed kitchen: once you understand the layout, everything has a logical place and the workflow feels natural.

When we started looking at GitHub integration, the OrchestrationEngine pattern we’d been developing basically told us exactly what to do:

  1. Task handlers manage specific types of work
  2. Context enrichment happens during workflow creation
  3. Repository pattern keeps external integrations separate from business logic
  4. Error handling follows established domain contracts

We didn’t have to invent new patterns. We just had to follow the ones we’d already discovered worked.

The pattern recognition — seeing how GitHub integration could fit into our existing approach —came from having clean, consistent architecture to work from.

The internal task handler revelation

The first decision point was where to put the GitHub integration logic. In our earlier technical debt cleanup, we’d established that OrchestrationEngine was our canonical orchestration system. But how should new task types connect to it? We had three choices:

  1. Create separate GitHubTaskHandler classes
  2. Build handlers as internal methods within OrchestrationEngine
  3. Create some hybrid plugin architecture

When we looked at how file analysis was already integrated, the answer was obvious: OrchestrationEngine used internal methods, not separate handler classes.

# The pattern we'd already established:
self.task_handlers = {
TaskType.ANALYZE_FILE: self._analyze_file,
TaskType.PROCESS_CONTENT: self._process_content
}

# So GitHub integration became:
self.task_handlers[TaskType.GITHUB_CREATE_ISSUE] = self._create_github_issue

This wasn’t a grand architectural decision — it was just following the pattern that was already working.

The context enrichment pattern

The second insight came when we thought about user experience. Nobody wants to specify which repository to create an issue in every time they ask for a ticket. The system should just know.

But how do you “just know” which repository to use without requiring users to specify it every time?

The answer was hiding in plain sight: we already had a context enrichment pattern from file analysis. When a workflow gets created, we automatically look up and add relevant context that the task handlers will need.

For GitHub integration, that meant:

  • Look up the project’s GitHub integration configuration
  • Find the repository associated with the project
  • Add that repository context to the workflow automatically
  • Let the task handler just use the context that’s already there
# Context enrichment during workflow creation
if workflow.type == WorkflowType.CREATE_TICKET:
github_config = await self._get_github_config(project_id)
if github_config:
workflow.context["repository"] = github_config.repository

Seamless user experience through systematic context management.

When patterns accelerate development

Here’s what was remarkable about following our established patterns: the implementation work became almost mechanical.

Task registration: Add the task type to the enum, map it to the handler method

Handler implementation: Follow the established signature (workflow, task) → TaskResult

Error handling: Return results with error metadata, never throw exceptions

Testing: Write tests that verify the handler behavior, not the internal implementation

We didn’t have to make architectural decisions because the architecture had already been decided. We didn’t have to debate error handling approaches because we’d already established the domain contract. We didn’t have to figure out how to structure tests because we had working examples.

The result: GitHub integration that felt like a natural extension of the system rather than something bolted on.

The repository context enrichment insight

The most elegant part of the implementation was the repository context enrichment. Users can just say “create a ticket” without specifying where, and the system figures it out automatically.

But here’s the architectural insight: this works because we designed it as a non-blocking enrichment. If the system can’t find GitHub configuration for a project, it doesn’t fail the workflow creation — it just logs a warning and lets the task handler deal with the missing context gracefully.

This pattern enables a much better user experience:

  • Power users get automatic repository detection
  • New users get helpful error messages about missing configuration
  • The system remains functional even when integrations aren’t perfectly set up
# Non-blocking enrichment pattern
try:
repository = await self._lookup_repository(project_id)
workflow.context["repository"] = repository
except ConfigurationError:
logger.warning(f"No GitHub repository configured for project {project_id}")
# Workflow continues without repository context

Graceful degradation built into the architecture from the start.

Getting disciplined about documentation

Once the implementation was working, we had a choice: ship it and move on, or take the time to document what we’d learned.

This is where following your own patterns really pays off. We’d established during our technical debt cleanup that architectural discoveries should be documented immediately, not “when we have time later.”

So we updated six different documentation files:

  • architecture.md: GitHub integration status and new patterns
  • pattern-catalog.md: Internal Task Handler Pattern and Repository Context Enrichment Pattern
  • technical-spec.md: Orchestration details and GitHub examples
  • data-model.md: Workflow context structures
  • api-reference.md: GitHub-specific usage examples
  • api-specification.md: Complete API contracts

Six files. In one session. Because we’d learned that architectural insights are fragile — if you don’t capture them immediately, you lose the context that made them insights in the first place. Plus, let’s face it, bots write docs fast.

Pattern discovery vs. pattern following

What struck me about this session was the difference between discovering patterns (what we’d been doing in earlier development) and following patterns (what we were doing now).

Pattern discovery is hard work: lots of false starts, architectural decisions, refactoring when you realize your first approach doesn’t scale. Pattern following is… well, it’s almost easy. Not because the work itself is simple, but because the decisions have already been made. You’re implementing, not architecting.

This is what good architecture gets you: it makes the next feature obvious.

The compound effects of consistency

By following established patterns instead of inventing new ones, we got several benefits we didn’t expect:

Testing became routine: Same patterns as file analysis testing, just different inputs and outputs

Error handling was automatic: Domain contract already established, just needed to follow it

Integration felt natural: New functionality flowed through existing orchestration without friction

Documentation was straightforward: Adding to established pattern catalogs rather than documenting entirely new approaches

Most importantly: the next developer (including future us) would immediately understand how GitHub integration worked, because it followed the same patterns as everything else in the system.

When architecture becomes invisible

The best architecture is invisible architecture. Not because you can’t see it, but because it feels so natural that you stop thinking about it.

During this GitHub integration session, we spent very little time making architectural decisions. Instead, we spent time implementing features, writing tests, and documenting patterns.

The architecture had become the foundation for work rather than the subject of work.

This is what systematic architectural cleanup gets you: the ability to focus on solving user problems instead of solving architectural problems.

The “Internal Task Handler” pattern

One of the patterns we documented was the “Internal Task Handler Pattern” — the decision to implement task handlers as methods within OrchestrationEngine rather than separate classes.

This wasn’t a grand architectural vision. It emerged from practical considerations:

  • Simpler architecture: Fewer files, fewer abstractions to maintain
  • Direct access: Task handlers can access engine state and dependencies directly
  • Easier testing: Test the orchestration engine as a unit, not a collection of loosely coupled handlers

But documenting it as a pattern meant future integrations could follow the same approach without having to rediscover the reasoning.

Sometimes the best architectural decisions are the ones you don’t have to make because someone (even if it was past you) already made them thoughtfully.

The “Repository Context Enrichment” pattern

The other pattern we documented was “Repository Context Enrichment” — the automatic lookup and injection of GitHub repository information during workflow creation.

This pattern enables the “just works” user experience: say “create a ticket” and the system figures out where to create it based on your project configuration.

But the pattern is more general: context enrichment can apply to any external integration where users shouldn’t have to specify configuration details every time they want to use a feature.

Future integrations (Slack, Jira, email) can follow the same pattern: look up configuration during workflow creation, inject context automatically, handle missing configuration gracefully.

What following patterns teaches you

Following your own patterns teaches you things about your system that you didn’t know you knew.

Consistency compounds: When every integration follows the same approach, the cognitive load for adding new integrations decreases over time

Documentation matters: Patterns only help if they’re documented well enough for future developers to follow

Error handling is architecture: How you handle missing configuration or failed operations is as important as how you handle success cases

User experience is systematic: Good UX comes from consistent system behavior, not just individual feature polish

Most importantly: good patterns make good features feel inevitable. Users don’t think “wow, the GitHub integration is so well-architected.” They think “of course creating tickets works exactly like I expected it to.”

The velocity paradox: speed soothes

Here’s the paradox of following patterns: the session where we added GitHub integration felt almost boring compared to our earlier architectural discovery sessions.

No major insights. No breakthrough moments. No complex problems to solve. Just… implementing a feature according to established patterns and documenting what we learned.

But that “boring” implementation session was actually the payoff for all the earlier architectural work. The ability to add significant functionality without drama is what good architecture provides.

Sometimes the most productive sessions are the ones where you don’t have to solve any architectural problems because you’ve already solved them.

Building pattern literacy

One thing this session reminded me is that pattern literacy — the ability to recognize and follow established approaches — is a different skill from pattern discovery. And I’m an old pattern guy from way back!

Pattern discovery requires architectural thinking, systems perspective, willingness to experiment and refactor.

Pattern following requires discipline, attention to detail, and the humility to implement rather than innovate when innovation isn’t needed.

Both skills are essential. But knowing when to use which skill might be the most important skill of all.

The systematic documentation payoff

Updating six documentation files in one session might seem like overhead, but it paid immediate dividends. When we tested the GitHub integration the next day and ran into issues, the documentation we’d written helped us debug problems and understand the intended behavior.

Good documentation isn’t just helpful for other developers — it’s helpful for you, debugging your own code weeks later when you’ve forgotten the design decisions that seemed obvious at the time.

Plus, systematic documentation discipline means you never lose architectural insights. The Internal Task Handler Pattern and Repository Context Enrichment Pattern are now part of our permanent architectural toolkit, not just “something we did that one time.”

The series arc

Looking back at the sequence of sessions — technical debt cleanup, test guidance, and now systematic implementation — I can see how each session built on the previous ones.

Clean architecture (from debt cleanup) enabled test guidance (domain contract clarity) which enabled pattern following (systematic implementation).

None of these sessions would have worked as well in isolation. But together, they created a compound effect where each improvement made the next improvement easier.

When good architecture feels effortless

The best measure of good architecture isn’t how impressive it looks in diagrams. It’s how effortless it makes future development feel.

After this GitHub integration session, I had confidence that the next integration (whatever it might be) would follow similar patterns and feel similarly straightforward. Not because the individual features would be simple, but because the architectural decisions wouldn’t need to be re-made.

This is what “following your own patterns” really means: building on your past good decisions instead of starting over every time.

← Back to BlogAlso available on Medium