runward 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +13 -0
  3. package/README.md +128 -0
  4. package/dist/cli.js +76 -0
  5. package/dist/commands/check.js +50 -0
  6. package/dist/commands/doctor.js +75 -0
  7. package/dist/commands/init.js +90 -0
  8. package/dist/commands/status.js +49 -0
  9. package/dist/commands/update.js +48 -0
  10. package/dist/lib/constants.js +2 -0
  11. package/dist/lib/mission.js +96 -0
  12. package/dist/lib/paths.js +28 -0
  13. package/dist/lib/styles.js +60 -0
  14. package/dist/lib/tools.js +59 -0
  15. package/dist/lib/write.js +30 -0
  16. package/package.json +29 -0
  17. package/templates/mission/adr/ADR-0000-template.md +38 -0
  18. package/templates/mission/architecture.md +60 -0
  19. package/templates/mission/decision-matrix.md +37 -0
  20. package/templates/mission/evaluation-rubric.md +60 -0
  21. package/templates/mission/floor.md +47 -0
  22. package/templates/mission/framing.md +58 -0
  23. package/templates/mission/mission-contract.md +58 -0
  24. package/templates/mission/observability-schema.md +53 -0
  25. package/templates/mission/port-contract.md +63 -0
  26. package/templates/mission/reference-stack.md +70 -0
  27. package/templates/mission/runbook.md +70 -0
  28. package/templates/mission/shared-bricks.md +74 -0
  29. package/templates/mission/threat-model.md +53 -0
  30. package/templates/rules/async-job-guardrails.md +95 -0
  31. package/templates/rules/async-post-turn-pipeline.md +96 -0
  32. package/templates/rules/async-scheduled-maintenance.md +95 -0
  33. package/templates/rules/cache-three-tier-architecture.md +44 -0
  34. package/templates/rules/checklist-day-zero-project.md +97 -0
  35. package/templates/rules/checklist-pre-production-observability.md +99 -0
  36. package/templates/rules/checklist-pre-production-performance.md +109 -0
  37. package/templates/rules/checklist-pre-production-resilience.md +99 -0
  38. package/templates/rules/checklist-pre-production-security.md +86 -0
  39. package/templates/rules/config-secrets-boundary.md +57 -0
  40. package/templates/rules/config-typing-zod.md +78 -0
  41. package/templates/rules/contracts-governance.md +64 -0
  42. package/templates/rules/data-memory-consolidation.md +81 -0
  43. package/templates/rules/data-memory-invalidation.md +90 -0
  44. package/templates/rules/data-memory-scoring.md +154 -0
  45. package/templates/rules/data-migrations-forward-only.md +55 -0
  46. package/templates/rules/data-orphan-cleanup.md +142 -0
  47. package/templates/rules/data-ttl-types.md +100 -0
  48. package/templates/rules/eval-loop.md +52 -0
  49. package/templates/rules/frontier-deterministic-boundary.md +91 -0
  50. package/templates/rules/hexa-adapter-pattern.md +85 -0
  51. package/templates/rules/hexa-architecture.md +84 -0
  52. package/templates/rules/hexa-move-deterministic-out.md +93 -0
  53. package/templates/rules/hexa-recommended-stack.md +33 -0
  54. package/templates/rules/hexa-typescript-native.md +80 -0
  55. package/templates/rules/observability-alert-configuration.md +160 -0
  56. package/templates/rules/observability-llm-metrics.md +150 -0
  57. package/templates/rules/observability-startup-provider-log.md +128 -0
  58. package/templates/rules/observability-structured-json-logs.md +138 -0
  59. package/templates/rules/patterns-memory-router-tiered.md +139 -0
  60. package/templates/rules/patterns-prompt-compiler.md +126 -0
  61. package/templates/rules/patterns-request-id-propagation.md +137 -0
  62. package/templates/rules/process-adr-and-journal.md +50 -0
  63. package/templates/rules/provider-llm-auto-detection.md +54 -0
  64. package/templates/rules/provider-no-crash-missing-env.md +67 -0
  65. package/templates/rules/quality-codebase-metrics.md +115 -0
  66. package/templates/rules/quality-zod-input-validation.md +131 -0
  67. package/templates/rules/resilience-fail-open.md +65 -0
  68. package/templates/rules/resilience-multi-provider-fallback.md +100 -0
  69. package/templates/rules/resilience-retry-backoff.md +115 -0
  70. package/templates/rules/resilience-retryable-errors.md +107 -0
  71. package/templates/rules/routing-confidence-upgrade.md +89 -0
  72. package/templates/rules/routing-model-cost-ratios.md +68 -0
  73. package/templates/rules/routing-smart-complexity.md +131 -0
  74. package/templates/rules/scaling-db-connection-pooling.md +121 -0
  75. package/templates/rules/scaling-distributed-rate-limiting.md +141 -0
  76. package/templates/rules/scaling-state-externalization.md +84 -0
  77. package/templates/rules/security-prompt-injection.md +63 -0
  78. package/templates/rules/state-event-sourcing.md +66 -0
  79. package/templates/rules/tools-registry-pattern.md +153 -0
  80. package/templates/rules/tools-scope-atomicity.md +103 -0
  81. package/templates/targets/AGENTS.md +32 -0
  82. package/templates/workflows/architect.md +50 -0
  83. package/templates/workflows/brownfield.md +52 -0
  84. package/templates/workflows/decision-loop.md +52 -0
  85. package/templates/workflows/floor.md +60 -0
  86. package/templates/workflows/frame.md +69 -0
  87. package/templates/workflows/govern.md +55 -0
  88. package/templates/workflows/handover.md +55 -0
  89. package/templates/workflows/iterate.md +52 -0
  90. package/templates/workflows/method.md +53 -0
  91. package/templates/workflows/review.md +59 -0
@@ -0,0 +1,58 @@
1
+ # Mission Contract: [system or mission name]
2
+
3
+ > **Usage.** One page, filled with the sponsor at framing; the steering contract of the mission. It states what is delivered, on what condition it is accepted, and along which roadmap. It links the success criterion to the deliverables and the milestones, and it is the shared reference from one gate to the next. Replace every `[placeholder]`; delete this notice on delivery.
4
+
5
+ **Date**: [YYYY-MM-DD] · **Sponsor**: [name or role] · **Operator(s)**: [name(s)] · **Indicative horizon**: [e.g. framing in days, floor in weeks]
6
+
7
+ ## Principle
8
+
9
+ A deliverable is judged by its **acceptance against an observable criterion**, not by its form. Every engagement therefore carries two inseparable faces: what is handed over, and the condition that says it is done. That condition — the Definition of Done — always ties back to the success criterion set at framing. This protects both sides: no fuzzy scope, no deliverable open to interpretation.
10
+
11
+ ## Engagements
12
+
13
+ | Engagement | Deliverables | Definition of Done |
14
+ |---|---|---|
15
+ | **Flash framing** | Framing note: problem, target value, observable success criterion, floor vs target split, first costed increment, hard constraints | The sponsor validates the success criterion and the floor scope; what is deferred is named |
16
+ | **Executable floor** | System wired to real traffic; the deterministic taken out of the model as testable code; baseline observability; light architecture note | The system answers on real cases and a first proof of value is measured against the success criterion |
17
+ | **Staged iteration** | Increments delivered; structural decisions locked as ADRs; governance, security and evaluation instrumented | Every increment is measured, every added complexity traced to a trigger, every decision traceable |
18
+ | **Handover** | Reusable assets, tutorials, handover sessions; evidence in the code; recovery documentation | The team takes over and evolves the system without the operator — demonstrated on a task redone autonomously |
19
+
20
+ ## Acceptance of the whole mission
21
+
22
+ Beyond each engagement, the mission is done when these four conditions hold. This is the global acceptance — the one you sign.
23
+
24
+ 1. A system **holds in production on real traffic**, not in a demo.
25
+ 2. The success criterion set at framing is **measured and reached**, or its gap is explained.
26
+ 3. **Governance is in place**: observability, provenance, human approval, untrusted inputs constrained.
27
+ 4. The assets are **handed over and the team is autonomous** — demonstrated, not declared.
28
+
29
+ ## Typical roadmap and decision gates
30
+
31
+ A mission follows a constant arc, punctuated by decision gates: a milestone is crossed only when the previous one has proven itself. Durations are indicative and set at framing.
32
+
33
+ ```
34
+ DoR check --> Framing --> Executable floor --> Increments --> Handover
35
+ (launch (days) (weeks) (iterative, (autonomy)
36
+ conditions) on evidence)
37
+ gate: floor proven? gate: increment holds?
38
+ ```
39
+
40
+ A gate is crossed on **measured evidence**, never by calendar or by principle.
41
+
42
+ - **After the floor**: is the first proof of value there? If not, fix the floor — do not add complexity.
43
+ - **After each increment**: is the gain measured and held? The next increment is justified only by an objective trigger.
44
+ - **Before handover**: is the system governed and the team ready to take over?
45
+
46
+ ## The contract, to fill with the sponsor
47
+
48
+ | Field | To fill in |
49
+ |---|---|
50
+ | **Problem** | [The real need, not the dreamed one — as observed in the process] |
51
+ | **Success criterion** | [Observable and measurable on real traffic] |
52
+ | **Floor** | [The smallest system that proves value] |
53
+ | **Target** | [The full architecture aimed at, by increments — named, not built] |
54
+ | **Engagements retained** | [flash framing / executable floor / staged iteration / handover — which apply] |
55
+ | **Milestones & gates** | [Indicative dates and the passing condition of each gate] |
56
+ | **Deliverables & DoD** | [Taken from the engagements table, adjusted to this mission] |
57
+ | **Hard constraints** | [Sovereignty, regulation, security, legacy — whatever bounds the solution space] |
58
+ | **Risks owned by the sponsor** | [Missing launch conditions (DoR), if any, each named as an owned risk] |
@@ -0,0 +1,53 @@
1
+ # Observability Schema: [system or agent name]
2
+
3
+ > **Usage.** An unobserved agentic system is ungovernable and unpredictable in cost. Observability is a design property carried by the middleware chain, never an end-of-project option. Three independent levels: structured logs, lifecycle events, per-model-call metrics. One request ID propagated from entry to every tool and model call — and to sub-agents via parent/child lineage — makes every trajectory replayable and auditable. The same trace stream also feeds continuous evaluation and provenance. Auditing a past decision means rebuilding the context as it was: the schema must let you unfold a consolidated memory back to its raw facts at the exact timestamp. Cost is steered by architecture, with explicit ceilings. Replace every `[placeholder]`; delete this notice on delivery.
4
+
5
+ **Version**: [vX.Y] · **Last review**: [YYYY-MM-DD]
6
+
7
+ ## 1. The three levels
8
+
9
+ [What is emitted at each level, and where it aggregates.]
10
+
11
+ | Level | What | Fields | Use |
12
+ |---|---|---|---|
13
+ | **Structured logs** | one line per event, typed context | [module, request ID, session, timestamp] | [aggregation, search, alerting] |
14
+ | **Lifecycle events** | every orchestrator step and tool call, persisted | [step, tool, input, output, status] | [trajectory replay, behavioral audit] |
15
+ | **Per-model-call metrics** | one measurement per inference | [input tokens, output tokens, cache, tier used, duration, status, attempt] | [cost and performance tracking per agent] |
16
+
17
+ ## 2. Propagated request ID
18
+
19
+ [The single identifier that threads the whole trajectory.]
20
+
21
+ - **Origin**: [generated at system entry.]
22
+ - **Propagation**: [passed to every tool and model call.]
23
+ - **Parent/child lineage**: [propagated to sub-agents to reconstruct a task's tree.]
24
+ - **Carrier field**: [field name in logs, events, and metrics.]
25
+
26
+ ## 3. Provenance
27
+
28
+ [What makes it possible to replay exactly what the model saw, even after working memory is gone.]
29
+
30
+ - **Per-inference fingerprint**: [what was actually injected into context on each call.]
31
+ - **Associated versions**: [prompt and model version behind each output, for temporal consistency.]
32
+ - **Linkage**: [how provenance ties to lifecycle events and metrics through the same request ID.]
33
+
34
+ ## 4. Unfolding a consolidated memory (audit)
35
+
36
+ [The condition for an audit that holds: rebuild the context of a decision, not just observe that a call happened.]
37
+
38
+ - **Consolidation pointers**: [when a memory item is consolidated, deposit pointers to its raw source facts.]
39
+ - **Unfolding procedure**: [from a compressed item, walk the pointers back to the raw facts at the exact timestamp of the decision.]
40
+ - **Principle**: [current execution stays light; explaining a past decision stays possible after summarization.]
41
+
42
+ ## 5. Cost ceilings
43
+
44
+ [Recurring cost as a steered quantity, not a suffered one — from day zero.]
45
+
46
+ - **Aggregate counter**: [per root task.]
47
+ - **Ceilings per time window**: [thresholds set in advance.]
48
+ - **Behavior on overrun**: [the orchestrator stops and returns a synthesis, instead of running open-ended.]
49
+ - **Structural cost levers**: [the model boundary (deterministic work pays no call); tier routing (fast / balanced / deep — one tier up when in doubt); caching and prompt stability (stability beats raw token trimming).]
50
+
51
+ ## References
52
+
53
+ - [Evaluation rubric (fed by this trace stream), runbook, related ADRs.]
@@ -0,0 +1,63 @@
1
+ # Port Contract: [port name]
2
+
3
+ > **Usage.** A port expresses a domain need as a contract: provide a model completion, persist state, execute an action, read a source. The adapter that implements it may be written in any language, as long as it honors the contract. This spec describes the contract, never the implementation. Two reminders: the typed schema guards the data, but the behavioral boundary is validated (see the evaluation rubric and contract tests), not guaranteed by types; and a stable contract is not a frozen one — it is governed, it carries meaning, and it composes with legacy. The "evolution rule" section is not optional. Replace every `[placeholder]`; delete this notice on delivery.
4
+
5
+ ## Port: [e.g. ModelPort, PersistencePort, ActionPort, SourcePort]
6
+
7
+ **Contract version**: [vX.Y]
8
+ **Port type**: [primary (drives the domain) | secondary (driven by the domain)]
9
+ **Known adapters**: [implementations, e.g. gateway adapter, store adapter]
10
+
11
+ ## Business intent
12
+
13
+ [What the domain asks for, in business language, no technical detail. Why the need exists. Pin the meaning of the terms used — "client", "account", "validation" do not mean the same thing everywhere; fix this bounded context's meaning to prevent hallucinated mappings.]
14
+
15
+ ## Signature
16
+
17
+ [The operation(s) the port exposes. For each: name, role, idempotent or not, sync or async, and whether it declares that approval is required.]
18
+
19
+ ```
20
+ [operation]([input]) -> [output]
21
+ ```
22
+
23
+ ## Input schema
24
+
25
+ [Fields, types, optionality, per-field invariants. The schema is the guard: a valid payload passes, a malformed one is rejected at the boundary.]
26
+
27
+ | Field | Type | Required | Constraint |
28
+ |---|---|---|---|
29
+ | [field] | [type] | [yes / no] | [constraint] |
30
+
31
+ ## Output schema
32
+
33
+ [Same columns. State what is guaranteed present and what is optional.]
34
+
35
+ | Field | Type | Always present | Constraint |
36
+ |---|---|---|---|
37
+ | [field] | [type] | [yes / no] | [constraint] |
38
+
39
+ ## Invariants
40
+
41
+ [Properties true before and after the operation, independent of implementation. E.g.: every output references its input; a produced ID is unique; a read never mutates state. Invariants, not types, define the deep contract.]
42
+
43
+ ## Errors
44
+
45
+ [Declared failure modes, qualified by type so the consumer knows how to react.]
46
+
47
+ | Error | Type | Meaning for the consumer |
48
+ |---|---|---|
49
+ | [error] | [transient / validation / business / unavailable] | [retry, degraded mode, escalate, fall back] |
50
+
51
+ ## Evolution rule
52
+
53
+ [How this contract changes without breaking its consumers.]
54
+
55
+ - **Versioned**: [the contract carries a version; every evolution advances it.]
56
+ - **Additive by default**: [add optional fields; the consumer is a tolerant reader — it ignores unknown fields and accepts missing optional ones.]
57
+ - **Expand then contract**: [a genuinely breaking change happens in two steps: introduce the new, migrate consumers, retire the old once unused. Never in one move.]
58
+ - **Consumer-driven verification**: [each consumer expresses its expectations as an executable contract; a producer change that would break them fails at integration, before production.]
59
+ - **Provenance**: [record who produces, who consumes, under which version — so impact is measured before a change, not after.]
60
+
61
+ ## References
62
+
63
+ - [Related port, adapter, ADR, contract-test suite.]
@@ -0,0 +1,70 @@
1
+ # Reference Stack
2
+
3
+ This note is a **kit of default adapters**: enough to ship a first increment fast, without reopening every arbitration from scratch. Read it for what it is — and for what it is not.
4
+
5
+ > **Framing warning.** These choices are **reversible adapter decisions**, not dogma. The boundary is the domain ports and the inter-process tool protocol; behind it, an adapter's language and a store's technology are implementation details you replace without touching the business. A default holds until an objective trigger commands a change. The transverse rule is constant: start simple, isolate by contract, add complexity only on evidence.
6
+
7
+ The runnable implementation of these defaults lives in the reference floor (`floor-ts/` in this repo). This note decides; the floor shows.
8
+
9
+ ---
10
+
11
+ ## Summary table
12
+
13
+ | Layer | Recommended default | Evolution trigger |
14
+ |---|---|---|
15
+ | Core language | One typed language for orchestration and interface (e.g. TypeScript) | Never without a technical reason; polyglot goes through a sidecar or a service, never a mix inside the core. |
16
+ | Specialized capability | Sidecar in the library's language, exposed through the tool protocol | As soon as a capability depends on a mature ecosystem in another language (browser automation, scientific computing, specialized models). |
17
+ | Hot-path component | Stay in the core; compiled service behind a contract only on evidence | Latency or throughput proven insufficient on the hot path. |
18
+ | Model gateway | Single port, direct SDK, three tiers (fast / balanced / deep), governance and routing centralized | Never a heavy chain framework by default; refine routing when a task's measured quality demands it. |
19
+ | Persistence | Local first, in-memory single-instance state, immutable journal plus derived working view | Shared store as soon as a multi-instance trigger appears (load, availability, state shared across processes). |
20
+ | Observability | Structured logs, cycle events, per-call metrics, propagated request id | Active from the first increment; it gets completed, never retrofitted. |
21
+ | Tests & evaluation | Pyramid: unit without network, schema contract, integration through the DI container, behavioral evaluation at the top, as a continuous loop | Re-run the evaluation bench on any change touching memory, prompt or routing. |
22
+
23
+ ---
24
+
25
+ ## Layer by layer
26
+
27
+ ### Core language
28
+
29
+ One typed language for the agentic core (orchestrator, tool registry, use cases) and the interface avoids doubling the stack. Types are shared between back and front. The model abstraction stays thin — a direct SDK, not a heavy chain framework — to keep control of prompts, events and token cost. **Trigger:** never by habit; polyglot is justified by a library or a performance constraint, and then goes through a sidecar or a service.
30
+
31
+ ### Specialized capability
32
+
33
+ When a capability depends on a mature ecosystem elsewhere, isolate it in its own process, consumed by the core as a tool provider through the tool protocol. The cross-language boundary stays sharp and stable. **Trigger:** a mature library lives in another ecosystem and no acceptable equivalent exists in the core language.
34
+
35
+ ### Hot-path component
36
+
37
+ Stay in the core by default. When a component on the hot path (high-throughput gateway, heavy parallel processing, tight latency) genuinely needs a compiled language, expose it as an adapter or a service behind a contract, without contaminating the rest. **Trigger:** latency or throughput proven insufficient, measured on the real path, not anticipated.
38
+
39
+ ### Model gateway
40
+
41
+ The model is one adapter among others, behind the model port. The gateway centralizes governance and routing: a task's estimated complexity picks the fast, balanced or deep tier, and on doubt you go one tier up rather than risk a bad answer. Multi-provider fallback behind the same port covers unavailability, without rewrite.
42
+
43
+ **Default real adapter, agnostic by provider profiles.** The floor ships an OpenAI-compatible adapter that works out of the box: fill in the configuration and the app runs. The adapter stays neutral — it names no provider; a provider's quirks (required headers or fields) are resolved at assembly time by a **profile keyed on the base URL**. Adding a demanding provider is one table line, not an adapter change. A **deterministic no-key fallback** keeps development and tests off the network. And a **per-run cost cap, set from day zero**, stops and synthesizes on overrun.
44
+
45
+ **Trigger:** never a heavy chain framework by default. Refine routing when a task's measured quality demands it; route deterministic tasks down to cheaper tiers. Promote a new model through shadow deployment then staged rollout, never in one move.
46
+
47
+ ### Persistence
48
+
49
+ The agent is a stateless reducer; **state lives outside, in three layers**: an immutable interaction journal (truth, audit, replay), a derived working memory (which forgets), and prompt provenance (which reconciles). Single-instance, in-memory structures are enough for rate limits, idempotency, sessions, cache and progress. **Trigger:** move to a shared store when a multi-instance signal appears (load, availability, state shared across processes). That move changes the semantics — eventual consistency, compensation instead of a single transaction, explicit causal order, idempotency under concurrency — so you pay for it knowingly, on signal, never by default.
50
+
51
+ ### Observability
52
+
53
+ Structured logs, cycle events, per-model-call metrics, and a request id propagated everywhere, including to sub-agents through parent/child lineage. **The same trace feeds the dashboard, the continuous behavioral evaluation, and provenance.** Explicit cost caps per root task, stop-and-synthesize on overrun. **Trigger:** observability gets completed, never retrofitted; unfolding a consolidated memory back to its raw facts becomes necessary as soon as a regulated audit demands it.
54
+
55
+ ### Tests & evaluation
56
+
57
+ **Test the deterministic, evaluate the non-deterministic.** The pyramid: unit tests on the pure domain without network (mock model adapter), schema contract tests (valid / invalid, consumer-driven), integration through the DI container with mock adapters, behavioral evaluation at the top. Evaluation is not a final gate but a continuous loop, sampled off the hot path. Hybrid scoring: deterministic for whatever admits a guarantee, a grounded judge model only for the irreducibly behavioral, on a non-gameable hold-out. **Trigger:** re-run the bench on any change touching memory, prompt or routing; widen any self-tuning envelope only on real improvement, and never past the deterministic floor (safety, security, authorization, audit).
58
+
59
+ ---
60
+
61
+ ## What the stack does not decide
62
+
63
+ - It does not decide the **problem** or the **floor** — framing does (`frame` workflow), upstream.
64
+ - It does not make a **structural decision** without the ADR lock (`decision-loop` workflow): options, preference order, re-evaluation trigger.
65
+ - It fixes no **behavioral boundary**: the port covers the typed contract; behavior is validated separately, by evaluation.
66
+
67
+ ## References
68
+
69
+ - Implementation: the reference floor, `floor-ts/`.
70
+ - Related templates: `port-contract.md`, `runbook.md`, `observability-schema.md`, `evaluation-rubric.md` (this directory).
@@ -0,0 +1,70 @@
1
+ # Runbook: [system or agent name]
2
+
3
+ > **Usage.** This runbook is the transfer object: it lets a team that did not build the system start it, keep it running, and bring it back after an incident. Two principles behind it. Resilience is designed in by default, not after the incident: fail-open for the non-critical, fail-closed and explicit for the sensitive action. And the agent is a reducer with no hidden state — state lives outside: recovery rereads recorded outputs and restarts from a checkpoint; it never re-calls the non-deterministic model. Filling in contacts and the provider-failover procedure is not clerical work: it is what turns an incident into a non-event. Replace every `[placeholder]`; delete this notice on delivery.
4
+
5
+ **Version**: [vX.Y] · **Last review**: [YYYY-MM-DD] · **Owner**: [name or role]
6
+
7
+ ## 1. Startup
8
+
9
+ [The boot sequence, from the strict minimum to full mode.]
10
+
11
+ - **Prerequisites**: [environment, permissions, access, secrets expected in typed configuration.]
12
+ - **Start command**: [ordered steps.]
13
+ - **Feature detection at boot**: [the system detects which services are actually available; a missing dependency cleanly disables its feature instead of crashing.]
14
+ - **Health check**: [how to confirm the system is up. Signals to watch: model health, persistence health, sidecar health.]
15
+
16
+ ## 2. Dependencies and degraded modes
17
+
18
+ [For each dependency: its role, its behavior on failure, the associated degraded mode.]
19
+
20
+ | Dependency | Role | Criticality | Behavior on failure |
21
+ |---|---|---|---|
22
+ | [model gateway] | [generation] | [critical] | [failover to fallback provider, same port] |
23
+ | [persistence] | [state, log] | [critical] | [fail-closed on sensitive writes] |
24
+ | [capability sidecar] | [specialized capability] | [non-critical] | [circuit breaker, feature disabled and signaled] |
25
+ | [observability] | [traces, metrics] | [non-critical] | [silent degraded mode] |
26
+
27
+ **Transverse rule**: degrade reading, never acting. A sensitive action fails closed, explicit and traced, rather than executing in doubt.
28
+
29
+ ## 3. Checkpoints and recovery
30
+
31
+ [How state is held, and how to restart after a stop.]
32
+
33
+ - **State model**: [immutable interaction log (truth, audit); derived working memory (which forgets); prompt provenance (which reconciles).]
34
+ - **Recovery**: [restart from a checkpoint without replaying everything; reread recorded outputs; do not re-call the model.]
35
+ - **Replication**: [if multi-instance, externalized state lives in the shared store; another instance picks up the same explicit state.]
36
+ - **Agent suspended awaiting approval**: [state serialized durably, resources freed, agent rehydrated when the decision arrives — exactly where it stopped.]
37
+
38
+ ## 4. Common incidents
39
+
40
+ [Expected failures, qualified by error type, with the reaction.]
41
+
42
+ | Symptom | Error type | Diagnosis | Action |
43
+ |---|---|---|---|
44
+ | [timeout, overload] | [transient] | [check the dependency] | [retry with bounded exponential backoff] |
45
+ | [non-conforming output] | [validation] | [read the diagnostic] | [single retry, diagnostic fed back] |
46
+ | [missing resource] | [business] | [trace the trajectory via request ID] | [diagnose or escalate to a human] |
47
+ | [model provider silent] | [unavailable] | [provider health] | [failover — see section 6] |
48
+ | [cost overrun] | [ceiling] | [aggregate counter per root task] | [stop, synthesis returned] |
49
+
50
+ ## 5. Contacts
51
+
52
+ | Role | Person | Channel | Scope |
53
+ |---|---|---|---|
54
+ | [technical on-call] | [name] | [channel] | [operations] |
55
+ | [product owner] | [name] | [channel] | [business decisions] |
56
+ | [sensitive-action approver] | [name] | [channel] | [human validation] |
57
+ | [model infrastructure provider] | [contact] | [channel] | [escalation] |
58
+
59
+ ## 6. Model provider failover
60
+
61
+ [Keep availability failover sharply distinct from promoting a new model.]
62
+
63
+ - **Availability failover (immediate)**: [primary provider drops; automatic switch to the fallback behind the same port, no rewrite. Verify the fallback is healthy; watch for divergence.]
64
+ - **Manual override**: [how to force the switch when the automation does not trigger.]
65
+ - **Promoting a new model (earned)**: [never in one move. Validate in shadow deployment on real traffic; measure behavioral divergence with the same evaluation; roll out in stages with instant rollback on any regression.]
66
+ - **Rollback**: [how to return to the previous provider, and on which signal.]
67
+
68
+ ## References
69
+
70
+ - [Observability schema, threat model, evaluation rubric, related ADRs.]
@@ -0,0 +1,74 @@
1
+ # Shared Bricks
2
+
3
+ > **Usage.** Read this note when a capability the mission needs already exists — or should exist — beyond the application: a model gateway, a connector bank, a skill catalog, shared memory, a registry. It gives the placement families, the stable criteria, and a matrix of the common bricks, each with a sober default and its trigger. It decides *where a brick lives*; the port that consumes it is decided in `architecture.md`, and every placement switch is an ADR.
4
+
5
+ ## The move that does not change the rule
6
+
7
+ A brick leaving the application changes nothing for the domain. The application consumed it through a port before; it consumes it through the same port after. The only question is what stands behind the port — in-process code, a service the platform team runs, a managed product. That placement is a **reversible adapter decision**, arbitrated like every line of the decision matrix: one sober default, one explicit trigger, and an ADR when the trigger fires. If moving a brick would force the domain to change, the boundary was wrong — fix the port before moving anything.
8
+
9
+ ## The recursive pattern: contract, index, implementation
10
+
11
+ Every shared brick decomposes into three things that never merge:
12
+
13
+ - the **contract** — versioned, additive, tolerant reader — that consumers code against;
14
+ - the **index** that publishes and resolves what exists (which connectors, which skills, which models): it publishes and it resolves, it does not orchestrate. **The index is an index, not a brain.**
15
+ - the **implementation** behind the contract, replaceable without touching a single consumer.
16
+
17
+ This is the same discipline that keeps the tool registry thin inside the application, replayed at platform scale: **delivery delivers, the orchestrator decides.** A brick that starts deciding for its consumers has stopped being a brick and become a dependency you cannot leave.
18
+
19
+ ## Five placement families
20
+
21
+ | Family | What stands behind the port |
22
+ |---|---|
23
+ | **In-app** | A module of the application itself — the starting point for everything |
24
+ | **Existing infrastructure** | Infrastructure the organization already operates: API gateway, message bus, identity provider |
25
+ | **Dedicated internal platform** | A product run by a platform team, serving many applications |
26
+ | **Managed infrastructure service** | A cloud provider's neutral runtime: queues, workflow engines, observability stacks |
27
+ | **Managed model-vendor runtime** | The model vendor's own platform hosting agents, tools or memory |
28
+
29
+ Each family further from the top trades control for leverage. None is right in general; each is right under criteria.
30
+
31
+ ## Six stable criteria
32
+
33
+ The families change with the market; the criteria do not. Weigh every placement against:
34
+
35
+ 1. **Sovereignty & control** — who can read the data, change the behavior, revoke the access.
36
+ 2. **Reuse** — how many consumers actually benefit from sharing this brick.
37
+ 3. **Velocity** — what the placement does to the team's iteration speed, now and at handover.
38
+ 4. **Cost & dependency** — the bill, and the cost of leaving.
39
+ 5. **Reuse of existing** — what the organization already runs, staffs and knows how to audit outranks a new component of equal merit.
40
+ 6. **Compliance** — certifications, data residency, auditability the deployment must inherit.
41
+
42
+ ## The brick matrix
43
+
44
+ One sober default and one trigger per brick — the decision-matrix discipline applied beyond the application.
45
+
46
+ | Brick | Sober default | Trigger to move |
47
+ |---|---|---|
48
+ | **Model gateway** | One model port in-app, direct SDK | A second application needs the same routing, quotas and audit — shared gateway on existing infra or an internal platform |
49
+ | **Connector bank** | Connectors as in-app adapters | The same connector is being rewritten for a third consumer — publish it behind a contract, listed in an index |
50
+ | **Skill catalog** | Skills versioned in the app repository | Skills are shared across teams — a catalog with curation, provenance and versioning |
51
+ | **Shared memory** | Per-app memory: journal plus derived view | A second application needs the same facts — a memory service with access control per data class |
52
+ | **Discovery registry** | A static list in configuration | Entries change faster than releases, or third parties publish into it — a registry that publishes and resolves, still not a brain |
53
+ | **Durable execution** | In-app suspend-and-rehydrate on the journal | Several apps rebuild the same suspend/resume machinery — a workflow engine, managed or internal; it carries the control flow and the durability, never the reasoning or the business, and the model call stays a recorded activity you re-read, never replay |
54
+ | **Delivery & evaluation** | CI plus the eval harness per app | Rubrics and shadow deployments duplicated across teams — shared evaluation infrastructure; delivery delivers, the orchestrator decides |
55
+ | **Observability** | Structured logs and metrics per app, common schema | Cross-app trajectories must be reconstructed — a shared trace store, placed under the sovereignty rule below |
56
+ | **Identity & secrets** | The existing IdP and vault | Agents need their own principals at scale — an agent-identity extension of the existing IdP, never a parallel identity system |
57
+
58
+ **Reconciliation with the doctrine's defaults.** Where doctrine §15 leans on *existing infrastructure* as the sober default for the model gateway, the connector bank and observability — under criterion 5, what the organization already runs, staffs and audits outranks a new component of equal merit — this template defaults those three to **in-app, per-app** instead. The divergence is deliberate and narrow: the doctrine bricks assume an enterprise whose infrastructure is already operated, while a runward mission starts on a greenfield floor where no such infra exists to reuse yet. In-app is the honest starting point here, not a rejection of the criterion. The moment a second consumer appears — or an already-operated brick is on hand — criterion 5 reasserts itself and each brick's own trigger moves placement onto existing infrastructure. Same rule, different starting inventory.
59
+
60
+ ## Sovereignty, graduated by data class
61
+
62
+ Sovereignty is not one rule per brick; it is one rule per **class of data** crossing the brick. Public reference data can live on a managed vendor runtime; internal business data raises the bar; regulated or personal data pushes placement toward the internal families — and the class is decided at field level, not system level. Two facts are constantly underestimated: **traces are data** — an observability pipeline that ships prompts and tool arguments to a third party is exporting your most sensitive payloads under the name "telemetry"; and **third-party export is exfiltration** unless a decision says otherwise — same review as any data transfer: named recipient, named data class, named retention.
63
+
64
+ ## The usage registry
65
+
66
+ Keep a registry of who deploys what on which brick, because risk is classed **by deployment, not by platform**. The same internal platform hosts a harmless FAQ bot and an agent with write access to payments; a platform-level risk label would either strangle the first or wave the second through. Governance attaches to each deployment — its data classes, its action scopes, its approval points — and the registry is what lets you answer, at audit time, "what runs where, touching what."
67
+
68
+ ## The lesson of the layers
69
+
70
+ Every layer commoditizes. Yesterday's hand-rolled orchestration is today's managed workflow engine; today's bespoke gateway is tomorrow's product. The lesson is not to guess the winner — it is to **consume every brick through a contract and never weld the domain to a brick's SDK**. When the layer commoditizes, you swap the implementation and keep the contract; the applications above it do not notice.
71
+
72
+ ## A third party is untrusted input
73
+
74
+ The moment a brick admits third-party content — connectors, skills, registry entries, remote tools — **curation is not optional**: review before listing, provenance recorded, capability scopes declared, and the 2-of-3 rule applied to whatever the content can reach (see `governance/threat-model.md`). An index that lists anything unreviewed is not a registry; it is an attack surface with a search box.
@@ -0,0 +1,53 @@
1
+ # Threat Model: [system or agent name]
2
+
3
+ > **Usage.** This threat model maps the attack surfaces specific to an agentic system and fixes the guardrails. Principle: you do not defend against injection with detection — unreliable, especially for indirect injection — but with architecture. The model is not a trust boundary. The first-rank threat is prompt injection, intrinsic to any memory or retrieval. The worst case arises when a **lethal trifecta** — private-data access, untrusted-content ingestion, outbound communication — meets on one unguarded path; removing any one of the three defuses it. Operational rule: allow at most **two of the three while untrusted content is in the context window**; when all three are needed, the action goes under human supervision, never autonomous. Replace every `[placeholder]`; delete this notice on delivery.
4
+
5
+ **Version**: [vX.Y] · **Last review**: [YYYY-MM-DD] · **Agent privilege level**: [low | high]
6
+
7
+ ## 1. Attack surfaces
8
+
9
+ [List every way a hostile instruction or datum can enter or act.]
10
+
11
+ | Surface | Description | Trust | Primary risk |
12
+ |---|---|---|---|
13
+ | **Untrusted input (direct)** | [user input] | untrusted | [direct injection] |
14
+ | **Untrusted input (indirect)** | [retrieved content: page, document, memory item] | untrusted | [indirect injection, fired at ingestion] |
15
+ | **Memory** | [working memory, consolidated items] | untrusted by inheritance | [persisted injection, replayed later] |
16
+ | **Tools** | [tool registry exposed to the model] | guarded | [unauthorized call of an impactful tool] |
17
+ | **Exposed surface** | [tool server published by the system] | guarded | [unauthenticated access, unbounded rate] |
18
+ | **Secrets and sensitive data** | [configuration, private data] | internal | [exfiltration] |
19
+
20
+ ## 2. Lethal trifecta
21
+
22
+ [Assess the three properties on every sensitive path. The danger is never one property alone; it is their meeting.]
23
+
24
+ | Path / context window | Private data | Untrusted content ingested | Outbound communication | Verdict |
25
+ |---|---|---|---|---|
26
+ | [path] | [yes / no] | [yes / no] | [yes / no] | [safe (2 of 3) / human supervision (3 of 3)] |
27
+
28
+ **Context-window rule**: at most two of the three properties at once **while untrusted content is present in the context window** (the scope is the context, not the session — a session may hold many contexts, and the risk exists exactly as long as the untrusted content does). If all three are genuinely required on one path, the action does not run autonomously — it goes under human validation. For every three-of-three path, document which property is removed, or why supervision is imposed instead.
29
+
30
+ ## 3. Guardrails
31
+
32
+ [The non-negotiable base as soon as external content is ingested.]
33
+
34
+ - **Separation of the untrusted**: [retrieved content treated as data, never as instruction; kept apart from instructions; purged from context after use.]
35
+ - **Least privilege on tools**: [the model sees only what it may call; the registry is filtered by role before the model sees it.]
36
+ - **Human approval**: [on consequential actions and any outbound communication.]
37
+ - **Output validation**: [tool and model outputs validated by schema before they act.]
38
+ - **Immutable log**: [any injected action stays traceable and reversible.]
39
+ - **High-privilege patterns (if applicable)**: [proportionate to risk: pre-approved action sets (action selector); plan frozen before exposure to tool outputs (plan-then-execute); isolation of untrusted processing; or a privileged planner split from a quarantined model that only reads the untrusted (dual model).]
40
+
41
+ ## 4. Approval points
42
+
43
+ [Actions that require human approval — declared in the tool's contract, enforced by infrastructure.]
44
+
45
+ | Action | Approval trigger | Presentation to the human | If no response |
46
+ |---|---|---|---|
47
+ | [mutation, write, external push] | [always / above threshold] | [deterministic summary, faithful to the tool's real arguments — never a model paraphrase] | [agent suspended in durable state, resources freed, rehydrated on decision] |
48
+
49
+ **Reminder**: the approval summary is itself an attack surface. It must be deterministic and faithful to the real arguments, or an injected action slips through in the batch. Group low-urgency requests into a prioritized queue with summaries, so approvers are not trained to rubber-stamp.
50
+
51
+ ## References
52
+
53
+ - [Related hardening ADR, observability schema, evaluation rubric.]
@@ -0,0 +1,95 @@
1
+ ---
2
+ title: Background Job Guardrails (Retry, Idempotency, Bounded Concurrency, Job Observability)
3
+ impact: HIGH
4
+ impactDescription: The four non-negotiables of any background job — bounded retry per step, idempotency under concurrency, capped and partitioned concurrency, and queue lag plus failure rate as first-class metrics
5
+ tags: [async, jobs, idempotency, concurrency, retry, observability]
6
+ ---
7
+
8
+ ## Background Job Guardrails
9
+
10
+ > **A background job without guardrails is an incident on a delay.**
11
+
12
+ Post-turn pipelines and maintenance crons run off the hot path, where nobody is watching. That is exactly why they need harder guardrails than the interactive turn, not softer ones. Four are non-negotiable — all four, on every job:
13
+
14
+ 1. **Bounded retry, per step.** Each step retries independently, with backoff and a hard cap. Exhausted jobs park in a dead-letter state for inspection; nothing retries forever.
15
+ 2. **Idempotency.** Every real queue delivers at-least-once, so duplicates are a certainty, not an edge case. Each job carries an idempotency key derived from its cause (`extract-facts:turn-123`), and execution begins with an **atomic claim** — a unique-constraint insert, never check-then-insert — so concurrent deliveries collapse to one execution.
16
+ 3. **Bounded concurrency.** A global cap protects downstream systems (the database, the model provider); a per-user partition keeps one heavy tenant from starving everyone else.
17
+ 4. **Job observability.** Queue lag and failure rate are first-class metrics with alerts, at the same severity as user-facing latency. A queue that grows silently for three days is an outage you learn about from the user.
18
+
19
+ **Incorrect:**
20
+
21
+ ```typescript
22
+ // No guardrails: each absence is a distinct production incident.
23
+ queue.on('job', async (job) => {
24
+ while (true) { // BAD: unbounded retry storm
25
+ try {
26
+ await handlers[job.type](job); // BAD: no idempotency key —
27
+ break; // redelivery = double-processing
28
+ } catch { /* retry immediately */ }
29
+ }
30
+ });
31
+
32
+ await Promise.all(pending.map(run)); // BAD: unbounded fan-out melts the DB
33
+ // BAD: and no metrics — the queue can rot for days unseen
34
+ ```
35
+
36
+ **Correct:**
37
+
38
+ ```typescript
39
+ async function runJob(job: Job): Promise<void> {
40
+ // 1. Idempotency: atomic claim — a concurrent duplicate loses the
41
+ // insert itself, not a racy check that both callers pass.
42
+ const claimed = await db.tryInsert('job_executions', {
43
+ key: job.idempotencyKey, // e.g. 'extract-facts:turn-123'
44
+ claimedAt: new Date(),
45
+ });
46
+ if (!claimed) {
47
+ metrics.increment('jobs.deduplicated', { type: job.type });
48
+ return;
49
+ }
50
+
51
+ // 2. Bounded concurrency: global cap plus per-user partition.
52
+ const slot = await semaphore.acquire({
53
+ global: 32,
54
+ partition: { key: job.userId, limit: 2 },
55
+ });
56
+
57
+ const startedAt = Date.now();
58
+ try {
59
+ // 3. Bounded retry with backoff; then dead-letter, never forever.
60
+ await withRetry(() => handlers[job.type](job), {
61
+ maxRetries: 3,
62
+ backoff: 'exponential',
63
+ });
64
+ metrics.histogram('jobs.duration_ms', Date.now() - startedAt, { type: job.type });
65
+ } catch (error) {
66
+ await deadLetter.park(job, error); // inspectable, replayable
67
+ metrics.increment('jobs.failed', { type: job.type });
68
+ } finally {
69
+ slot.release();
70
+ }
71
+ }
72
+
73
+ // 4. Job observability: lag and failure rate are first-class signals.
74
+ setInterval(async () => {
75
+ metrics.gauge('jobs.queue_lag_seconds', await queue.oldestPendingAgeSeconds());
76
+ metrics.gauge('jobs.failure_rate_15m', await queue.failureRate({ window: '15m' }));
77
+ }, 30_000);
78
+ // Alert: queue_lag > 300s or failure_rate > 5% —
79
+ // same severity as user-facing latency.
80
+ ```
81
+
82
+ **Why this matters:**
83
+
84
+ - **At-least-once is the contract** of every production queue. Without the atomic idempotency claim, the same fact gets extracted twice, the same reminder fires twice, the same erasure runs half twice — and under concurrency, a check-then-insert passes for both callers.
85
+ - **Unbounded retry is a self-inflicted outage**: a failing provider gets hammered by its own victims, and the queue amplifies the incident instead of absorbing it.
86
+ - **Unbounded fan-out** turns a backlog into a database or provider meltdown, and one bulk-importing tenant into everyone's problem.
87
+ - **Invisible background failure degrades the system in a way nobody can date**: memory quietly stops updating, summaries go stale, and by the time answers feel wrong the cause is weeks old. Queue lag and failure rate make the rot visible the hour it starts.
88
+
89
+ **Checklist:**
90
+
91
+ - [ ] Every step has bounded retry with backoff; exhausted jobs park in a dead-letter state, never loop forever.
92
+ - [ ] Every job carries an idempotency key derived from its cause; the claim is atomic and holds under concurrent delivery.
93
+ - [ ] Concurrency is capped globally and partitioned per user or tenant.
94
+ - [ ] Queue lag and failure rate are emitted and alerted on, at the same severity as user-facing metrics.
95
+ - [ ] Dead-lettered jobs are inspectable and replayable after the cause is fixed.