software Custom software.Built around your business.

Software engineering, end to end

Custom software. From the inside out.

We build custom applications, connect systems, and automate the work between them. From requirements meetings to reliable integrations, with clear logs and data your team can actually use.

This site, under the hood
One HTML fileNative browser APIsAutomated deployment

01 / THE PROCESS

The work, with the working shown.

Good systems start with listening. Follow a sample project from requirements meetings and shared notes into application logic, clear interfaces, integrations, and delivery.

  Engineering notes / a website, explainedRead. Inspect. Explore.

First, a conversation.

Discovery / analysis

We meet the people doing the work, ask questions, and take notes. We map the current process, agree what matters, and turn assumptions into decisions before choosing a solution.

AT THE BLACKBOARD / SAMPLE WORKSHOPOperations + finance + delivery
“We need to know where an order is, without checking three systems.”
Sketch the flow. Find the gaps.
Source systemsERP · files · partner API
One order viewsame words, shared status
Your teamsearch · understand · act

What if a sync fails? → A visible review queue.

What we heard
Updates arrive by email. Staff compare spreadsheets with the ERP. Customers call for the same status information.
What we need to learn
Which system owns the order? Who can see each field? What should happen when a sync fails?
Agreed first scope
One order view, a visible last-sync time, and a searchable history of changes. Start with one source system.
Acceptance criteria
A user sees only permitted orders. Every imported update has a traceable outcome. Failed updates are visible for review.
Output → A shared brief. Priorities, owners, acceptance criteria, and questions to resolve. Example workshop notes, not a client transcript.

Turn a requirement into working code.

Custom Python / example

We build focused tools around the requirements. For this website, “read clearly” includes a sensible heading structure. This small Python parser checks the markup; the button below checks the current page in your browser.

check_headings.py
from html.parser import HTMLParser
from pathlib import Path

class HeadingAudit(HTMLParser):
    def __init__(self):
        super().__init__()
        self.headings = []

    def handle_starttag(self, tag, attrs):
        if tag in ("h1", "h2", "h3"):
            self.headings.append(tag)

page = HeadingAudit()
page.feed(Path("index.html").read_text())
assert page.headings.count("h1") == 1
assert page.headings[0] == "h1"
Try it locally — save beside index.html, then run python check_headings.py.

Python’s HTMLParser ↗ reads the markup without another dependency. These checks confirm one main heading, placed before subheadings; they are a starting point for a wider accessibility review.

Make the rules explicit.

Django / backend

A project brief can become a business workflow. Before storing a request or scheduling work, check its input. A launch date before the start date is a useful error to catch at the boundary.

forms.py / application example
from django import forms

class ProjectBrief(forms.Form):
    email = forms.EmailField()
    start = forms.DateField()
    launch = forms.DateField()

    def clean(self):
        data = super().clean()
        start = data.get("start")
        launch = data.get("launch")

        if start and launch and launch < start:
            self.add_error(
                "launch", "Launch cannot precede the start."
            )
        return data
One rule, one place — validate with form.is_valid() before passing cleaned data to a service.

Django forms ↗ handle field validation and attach errors to the right input. Views handle the request; shared services handle the workflow. This example belongs in a Django application, not this static page.

Example output / validation

Start: 14 September · Launch: 12 Septemberlaunch → Launch cannot precede the start.Result: the request is not accepted until corrected.

A field-specific error the interface can display. The rule is checked before the workflow continues.

Less friction. More clarity.

HTML / frontend

Put that validated brief in front of a person. Start with a regular form, then enhance the interaction: replace just the form with its result, keeping validation on the server.

brief.html / Django template
<form id="brief-form"
      method="post"
      action="/briefs/"
      hx-post="/briefs/"
      hx-target="#brief-form"
      hx-swap="outerHTML">
    {% csrf_token %}
    {{ form.as_div }}
    <button type="submit">
        Let's build something
    </button>
</form>
A small enhancement — the endpoint returns a form fragment for HTMX, or a full page for a normal POST.

HTMX ↗ adds targeted requests and HTML updates. This application excerpt needs a Django endpoint and HTMX loaded in its parent page. Our homepage keeps its interactions in native JavaScript.

Interactive output / form preview

Try a launch date before the start date. This local preview shows the response a person would see; nothing is submitted.

Connect the data. Keep the story.

Integrations / logging

The order view from our workshop needs a consistent language. Translate each source’s fields into a shared model, reject unknown states, and record enough context to follow the result.

orders.py / mapping example
import logging

logger = logging.getLogger("integrations.orders")

def normalize_order(payload):
    states = {"shipped": "Dispatched", "held": "Needs review"}
    status = states.get(payload["state"])
    if status is None:
        raise ValueError("Unsupported order state")

    order = {
        "external_id": str(payload["id"]),
        "status": status,
    }
    logger.info("Order normalized", extra=order)
    return order
Normalize → record → present — a public example with two source states and no customer data.

A mapping function is one part of an integration. The surrounding service records success and failure, applies retry rules, and exposes a searchable history. Logs retain useful context while sensitive payload fields are redacted. See how the systems connect ↓

Example output / operations view

OrderSyncDetailsTrace
DEMO-101SucceededDispatchedsync-001
DEMO-102Needs attentionUnsupported source statesync-002
DEMO-103SucceededOrder needs reviewsync-003

3 example records.

Illustrative records. A successful sync can still carry a business status that needs review. The trace connects the visible record to its integration history.

Delivery is part of the product.

Ansible / devops

A good release is repeatable. Describe the desired files and permissions, then let the tooling bring the server into that state. This standalone publishing recipe uses an example web root.

publish.yaml / public recipe
- name: Publish a static page
  hosts: web
  become: true
  tasks:
    - name: Create the web root
      ansible.builtin.file:
        path: /var/www/example
        state: directory
        mode: "0755"

    - name: Put HTML in place
      ansible.builtin.copy:
        src: index.html
        dest: /var/www/example/index.html
        owner: root
        group: root
        mode: "0644"
Same input, same state — run with a configured web inventory group and SSH access with privilege escalation.

Ansible’s copy module ↗ manages the file and its permissions. Nginx serves it. The full deployment also configures HTTPS, checks Nginx before reloading, and schedules certificate renewal.

Example output / completed release

  1. Page copied with the expected permissions
  2. HTTPS configured
  3. Nginx configuration validated
  4. Site reloaded and certificate renewal scheduled

An illustration of the full deployment’s outcome, not a live server report. Each operation has a visible result in the deployment log.

We build AI into your platform.

Custom chat + agents + MCP

We build our own AI chat into the platform, connected to MCP servers that expose useful business tools. An agent can retrieve an order, explain an integration failure, or prepare an action using the current user’s permissions.

  1. We build your chat interface.

    A custom experience inside your product, with streaming replies, conversation history, and visible activity.

  2. We connect agents to MCP tools.

    Servers and tools that connect the agent to your application services, business data, and integrations.

  3. We build permissions and activity logs.

    User-aware access, confirmations for sensitive actions, and a readable history of requests, results, and changes.

order_lookup.json / MCP request
{
  "jsonrpc": "2.0",
  "id": "demo-request-001",
  "method": "tools/call",
  "params": {
    "name": "get_order_status",
    "arguments": {
      "order_id": "DEMO-101"
    }
  }
}
A business tool, with a defined input — an illustrative MCP request. The server checks access before returning data.

Our chat handles the conversation and presents results. MCP tools ↗ connect the agent to application services and integrations. The platform enforces permissions, records tool activity, and asks for confirmation where a workflow needs it.

Example output / structured tool data

{"order_id": "DEMO-101", "status": "Dispatched"}

The chat can turn this data into a clear answer and link to the underlying order. Tool logs let your team inspect what was requested, what came back, and what action followed.

Original examples, written for this walkthrough. No client code. Copy, adapt, and explore.BUILT WITH INTENTION ↗

01 Understand the operation

Meet the people, take notes, and agree the requirements. Make assumptions and open questions visible before development starts.

02 Keep the logic together

Clear validation and reusable services. The same rules apply in the website, the admin, and background jobs.

03 Make it operable

Presentable data, searchable integration logs, and repeatable releases. Your team should be able to see what happened and what needs attention.

02 / INTEGRATIONS & YOUR ECOSYSTEM

Your systems, connected. Your data, made clear.

We connect the tools your team and partners depend on. We make the exchange traceable, then turn it into screens, reports, and decisions people can use.

ERP & accountingCRM & salesCommercePaymentsPartner APIsFiles & reporting
  1. 01 / Understand the source

    Agree on the data.

    Map ownership, identifiers, permissions, and update frequency. Define what a successful exchange looks like with your team and the system provider.

  2. 02 / Make exchanges traceable

    Know what happened.

    Validate incoming data, track each outcome, and make failures visible. Use explicit retry rules and useful logs, without storing secrets in the history.

  3. 03 / Put it in people’s hands

    Make the data usable.

    Searchable records, clear statuses, reconciliation views, and reports. Your team can see what is current, what changed, and what needs attention.

03 / THE TOOLBOX

Good tools. Chosen for a reason.

The stack follows the problem. We favour established tools, clear responsibilities, and fewer moving parts. Each library should earn its place.

Business applications

Django & PostgreSQL

Portals, operational tools, and business workflows. Structured data with an administration interface people can use.

Why these tools?

Django brings forms, authentication, and an admin together. PostgreSQL provides relational storage and transactions for related writes.

Explore Django ↗

Human interfaces

Pure HTML/CSS/JS & HTMX

Readable pages, deliberate interactions, and interfaces that put the task first. Enhance where it makes a difference.

Why these tools?

HTML gives content structure; CSS shapes the experience; JavaScript adds local interactions. HTMX can update a small part of a server-rendered application without a separate client data layer.

Explore HTMX ↗

Delivery & operation

Deployment automation / Ansible

Explicit configuration and repeatable deployment. From files on a server to a site that is ready to serve.

Why these tools?

Ansible describes the desired server state. Nginx serves the website and handles HTTPS. Configuration stays readable and versioned.

Explore Ansible ↗

Connected workflows

Async tasks & integrations

Connect your systems, schedule background work, and trace every important exchange. Make your data consistent, visible, and useful.

Why these tools?

Background workers handle scheduled jobs and retries. Integration logs show what was sent, what came back, and what needs attention. Dashboards and admin screens make that history usable, with sensitive data redacted.

Explore Celery ↗

For this page: HTML + CSS + a little JavaScript. Application tools appear in the examples; the page has no runtime library dependencies.

04 / YOUR NEXT CHAPTER

What are you working on?

A new idea, a difficult integration, or a process that could work better. Start with the problem. We’ll take it from there.

Let’s build something