Skip to content

Agent-first Workflow

Plumego is not only a Go HTTP toolkit. It has a machine-readable control plane that tells both humans and AI coding assistants how to classify work before editing code.

Read Repository Boundaries first if you are still learning how ownership is classified. Use this page when the question is specifically how AI agents interact with the repository.

Most AI coding assistants write code that compiles and passes tests but violates architectural conventions — not because the model is wrong, but because the conventions live in human knowledge and team memory rather than in a form the model can read.

Plumego externalizes these conventions:

ConventionWhere it livesMachine-readable?
Which module owns this work typespecs/task-routing.yamlYes
Which imports are allowed between layersspecs/dependency-rules.yamlYes
What a correct change sequence looks likespecs/change-recipes/Yes
What a module promises locally<module>/module.yamlYes
What gates must pass before a change landsinternal/checks/*Yes — enforced in CI

Control-plane map from an agent perspective

Section titled “Control-plane map from an agent perspective”
specs/task-routing.yaml → route work to owning module before writing code
specs/dependency-rules.yaml → verify import boundaries are not violated
specs/change-recipes/ → follow a defined sequence for known change types
<module>/module.yaml → read local scope, risk, and validation rules
internal/checks/* → run boundary and manifest checkers before commit
make gates → run the full CI-equivalent gate locally
StepWhat to readFile
1Which module owns this work type?specs/task-routing.yaml
2Which imports are allowed?specs/dependency-rules.yaml
3Is there a predefined recipe?specs/change-recipes/<type>.yaml
4What does the module promise locally?<module>/module.yaml
5Which checkers apply?internal/checks/

Reading in this order means the agent classifies ownership before it opens any Go file. The classification step is intentionally front-loaded so that boundary violations appear in the plan, not in the diff.

specs/task-routing.yaml maps work descriptions to owning modules. An agent evaluating “add rate limiting to the inbound greet endpoint” can read the routing table to confirm this belongs in middleware (transport-layer rate limiting) rather than x/resilience (outbound circuit protection).

This is the same classification humans do in PR review — Plumego just makes the rule explicit enough that an agent can apply it before writing code.

Task description: “add rate limiting to the inbound greet endpoint”

Step 1 — read the routing rules from specs/task-routing.yaml:

# specs/task-routing.yaml (excerpt)
routing_rules:
stable_root_work:
intent: Change kernel, lifecycle, route structure, transport contracts,
transport middleware, auth primitives, or storage primitives.
destination: stable
packages:
- middleware # ← transport-layer rate limiting lives here
- core
- router
- contract
- security
- store
- health
- log
- metrics
extension_work:
intent: Change product capability, business feature, protocol adaptation,
or extension behavior.
destination: extension
primary_families:
- x/resilience # ← outbound circuit protection lives here
- x/tenant
- x/gateway
# ...

Step 2 — classify:

Signal in the taskRouting matchOwner
”inbound”Transport concern, not product/business logicstable_root_work
”rate limiting” on the inbound pathTransport middleware, not circuit breakermiddleware
”greet endpoint”Route registration in app-local wiringapp_wiring → consult reference/standard-service

Step 3 — confirm the owning module task entry:

tasks:
middleware:
start_with:
- middleware/module.yaml
- docs/modules/middleware/README.md
- docs/reference/canonical-style-guide.md
avoid:
- core
- contract

Result: the change belongs in middleware/, starting from middleware/module.yaml. x/resilience is for outbound circuit protection — a different classification. Opening x/resilience first would be an ownership error the routing table prevents.

The routing table does not replace reading code. It narrows the search space before the agent opens any Go file.

Try it — select a scenario to see the routing decision:

Given a task, which module does the routing table assign?

Select a scenario to see the classification:

specs/dependency-rules.yaml defines the allowed import directions between layers:

  • Stable roots may not import from x/*
  • x/* families may import from stable roots
  • App-local code may import from both

internal/checks/dependency-rules enforces this rule mechanically. If an agent adds an import that violates the boundary, the gate fails:

Terminal window
go run ./internal/checks/dependency-rules

This check runs as part of make gates and is required before any change lands. It does not rely on reviewer memory.

specs/change-recipes/ contains YAML files that define the correct execution sequence for known change types. Each recipe specifies: the scope, ordered steps, and stop conditions (things the recipe must not do).

RecipeUse when
add-http-endpoint.yamlAdding a new route and handler in app-local code
add-middleware.yamlAdding transport-only middleware to the middleware package
fix-bug.yamlLocalizing and fixing a defect with regression tests
http-endpoint-bugfix.yamlFixing a defect in HTTP handlers, route wiring, or transport contracts
symbol-change.yamlRenaming, removing, or changing the behavior of an exported symbol
new-extension-module.yamlCreating a new x/* capability family
new-stable-module.yamlAdding a new package to the stable roots
add-websocket-room.yamlAdding a new room type or connection policy to x/websocket
add-grpc-method.yamlAdding a gRPC method alongside an existing HTTP surface via x/rpc
add-ai-tool.yamlRegistering a new tool in x/ai/tool or wiring it into a session
add-acceptance-tests.yamlWriting pre-failing acceptance tests that define a task card’s done condition
tenant-policy-change.yamlChanging tenant resolution, policy evaluation, quota, or rate-limit behavior
stable-root-boundary-review.yamlReview-only safety check for stable-root boundary changes
analysis-only.yamlResearch and planning tasks — no file edits allowed
review-only.yamlCode review tasks — findings only, no patch

Worked example: adding a new HTTP endpoint

Section titled “Worked example: adding a new HTTP endpoint”

Task: “Add a GET /users/:id handler that returns user details.”

Step 1 — classify using specs/task-routing.yaml. The task is app-local HTTP feature work → routing entry is app_wiring, recipe is add-http-endpoint.

Step 2 — read the recipe:

# specs/change-recipes/add-http-endpoint.yaml (excerpt)
name: add-http-endpoint
scope: app-local HTTP feature work
steps:
- Read docs/reference/canonical-style-guide.md.
- Read specs/repo.yaml and the target module manifest.
- Open reference/standard-service/internal/app/routes.go.
- Add one explicit route registration line.
- Add or update a handler with net/http shape.
- Use contract.WriteError for structured error responses.
- Add or update focused tests near the changed handler or module.
- Run module tests, then repository quality gates if the change is cross-cutting.
stop_conditions:
- Do not add bootstrap behavior in x/*.
- Do not hide dependencies in request context.

Step 3 — follow the steps in order:

reference/standard-service/internal/app/routes.go
mux.Get("/users/:id", handlers.GetUser)
// reference/standard-service/internal/handlers/users.go
func GetUser(w http.ResponseWriter, r *http.Request) {
id := router.Param(r, "id")
user, err := svc.FindUser(r.Context(), id)
if err != nil {
_ = contract.WriteError(w, r, contract.NewErrorBuilder().
WithType(contract.TypeNotFound).
WithMessage("user not found").
Build())
return
}
_ = contract.WriteResponse(w, r, http.StatusOK, user, nil)
}

Step 4 — run validation per the recipe:

Terminal window
go test -race ./internal/...
go run ./internal/checks/dependency-rules

Result: the recipe removes ambiguity about where the handler lives, what response helpers to use, and which tests to run — without requiring reviewer memory for any of it.

An agent following a recipe reads the defined steps, completes each one, then runs the checkers specified in the recipe before considering the change done.

Running gates after agent-generated changes

Section titled “Running gates after agent-generated changes”

After any agent-generated change, run the full gate before pushing:

Terminal window
make gates

make gates mirrors CI. It includes:

  • gofmt -l . — format check
  • go vet ./... — static analysis
  • go test ./... — test suite
  • go run ./internal/checks/dependency-rules — boundary check
  • go run ./internal/checks/agent-workflow — workflow compliance
  • go run ./internal/checks/module-manifests — manifest consistency
  • go run ./internal/checks/reference-layout — reference app shape

If any check fails, fix the specific failure before re-running. Do not bypass with --no-verify or skip individual checkers.

If an agent’s change spans a stable root and an x/* extension — for example, a change that modifies middleware and x/resilience together — treat these as separate changes and classify each independently:

  1. Read specs/task-routing.yaml for each work type in the change
  2. Confirm the owning module for each
  3. If they belong to different owners, split the change before editing

Crossing a boundary in a single commit is not automatically wrong, but it must be deliberate: the PR description should explain why the change spans layers and which recipe governs each part.

Next questionPage
How is work classified in general?Repository Boundaries
Which stable root or x/* family should I open?Modules Overview
What does release posture mean for adoption?Release Posture