diff --git a/next.config.js b/next.config.js index 568fd04..91c48b8 100644 --- a/next.config.js +++ b/next.config.js @@ -15,6 +15,13 @@ const nextConfig = { }, ]; }, + async rewrites() { + return [ + // Unlisted talk deck (aug 2026): served from public/harness.html. + // Intentionally not linked from any nav. + { source: '/harness', destination: '/harness.html' }, + ]; + }, }; module.exports = nextConfig; diff --git a/public/harness.html b/public/harness.html new file mode 100644 index 0000000..64dc1cf --- /dev/null +++ b/public/harness.html @@ -0,0 +1,819 @@ + + + + + + +Agent = modell + harness + + + +
+
+ + +
+
agent = modell + harness
+
«Agent = Model + Harness» — LangChain
+ +
+ + +
+

Samme modell. Helt forskjellig resultat.

+
+
+ mandag + Kjørte testene med feil kommando, la koden i feil mappe, fant opp et mønster vi sluttet med i fjor +
+
+ tirsdag + Kjørte riktig testkommando, la filen i riktig mappe, stoppet før force push +
+
+
Samme modell.
+ +
+ + +
+

Harness: alt som ikke er modellen

+
+
+
harness = system prompt + tools + context + regler + feedback loops
+
    +
  • Copilot CLI er en harness: agent loop, tools, filsystem
  • +
  • Men: den er generisk. Den kjenner ikke repoet deres.
  • +
+
+
+ harness + tools + context + regler + feedback +
modell
+
+
+ +
+ + +
+

Tre nivåer

+
+
+
NIVÅ 0
+

Out of the box

+

Der vi står allerede

+
+
+
NIVÅ 1
+

Prose

+

Du forteller agenten ting: instructions, skills

+
+
+
NIVÅ 2
+

Programmatisk

+

Du garanterer ting: hooks

+
+
+
Prose er forslag. Kode er garantier.
+ +
+ + +
+
+
Nivå 1 · Prose
+

AGENTS.md: alltid i kontekst

+
+
+
AGENTS.md
+
## Kommandoer +- Test: pnpm test (én fil: pnpm test -- path) +- Lint: pnpm lint --fix +- Typecheck: pnpm typecheck # før du sier deg ferdig + +## Struktur +- apps/web Next.js-frontend +- packages/ui Delte komponenter, aldri uten review +- infra/ Terraform. Ikke rør uoppfordret.
## Konvensjoner +- Ingen default-eksporter +- Feil: Result-typen i core/result.ts, ikke throw +- Nye endepunkter trenger test i samme PR + +## Ikke gjør +- Ikke commit til main +- Ikke oppgrader avhengigheter som sidefeature
+
+

Leses automatisk hver session, fra git root og cwd · path-spesifikke regler: .github/instructions/*.instructions.md med applyTo-glob · leser også CLAUDE.md

+
+ +
+ + +
+
+
Nivå 1 · Prose
+

Skills: kunnskap on demand

+
+
+
.github/skills/deploy/SKILL.md
+
---
+name: deploy
+description: Slik deployer vi. Bruk ved
+  release, rollback eller feilsøk av pipeline.
+---
+
+
    +
  • Instructions er alltid med → tar plass i context. Skills lastes når de er relevante.
  • +
  • Agenten ser bare navn + description, henter resten ved behov
  • +
  • Interop: leser også .claude/skills/
  • +
  • Samme gate: custom agents (.agent.md, subagent med eget context window og begrensede tools)
  • +
+
+ +
+ + +
+
+
Nivå 2 · Programmatisk
+

Hvorfor hooks, og ikke bare prose?

+
+
+

«Ikke push til main» i markdown er et ønske, ikke en regel.

+
1

Prose drukner: jo lengre session, jo dårligere etterlevelse

+
2

Prose koster context. Hooks koster null: de kjører utenfor modellen, hver gang

+
3

Guardrails må være garantier

+

Forbehold: krasjer hooken → kallet blokkeres (fail-closed). Timer den ut → slipper gjennom. Sett timeout, hold den rask.

+

Prose er kultur, hooks er lovverk.

+
+ prompt→ + preToolUse
allow/deny/ask
→ + tool call→ + postToolUse→ + resultat→ + svar +
+
+
+
events · v1.0.80
+
sessionStart
+
sessionEnd
+
userPromptSubmitted
+
userPromptTransformed
+
postResult
+
preToolUse
+
postToolUse
+
postToolUseFailure
+
preMcpToolCall
+
errorOccurred
+
agentStop
+
subagentStop
+
subagentStart
+
preCompact
+
permissionRequest
+
+
+ +
+ + +
+
+
Nivå 2 · Programmatisk
+

Hooks i praksis (testet på versjonen vi kjører)

+
+
+
~/.copilot/hooks/guard.json
+
{
+  "version": 1,
+  "hooks": {
+    "preToolUse": [
+      { "hooks": [ {
+        "type": "command",
+        "command": "bash ~/.copilot/hooks/guard.sh",
+        "timeoutSec": 5
+      } ] }
+    ]
+  }
+}
+
+
+
guard.sh
+
#!/bin/bash
+IN=$(cat)
+if echo "$IN" | grep -q 'rm -rf'; then
+  echo '{"permissionDecision":"deny",
+    "permissionDecisionReason":
+      "rm -rf er ikke lov her."}'
+fi
+exit 0
+
+
+
Denied by preToolUse hook: rm -rf er ikke lov her.
+
+
+

Typisk bruk

+

Guardrails med deny (rm -rf, git push --force, prod-credentials) · formatter/tester etter edit · context inn ved sessionStart

+
+
+

Sikkerhet

+

Repo-hooks kjører først når mappa er trusted · enterprise-policyer i policy.d kan ikke skrus av · disableAllHooks finnes

+
+
+

debug: /env viser lastede hooks · stdout → ~/.copilot/logs/ · lastes ved session-start · Windows: egne bash- og powershell-nøkler

+ +
+ + +
+
+
Nivå 2 · Programmatisk
+

Fra min egen verktøykasse

+
+
+
+
tiering-guard.sh
+

Denier subagent-kall uten eksplisitt modellvalg. Deny-meldingen inneholder regelen → agenten leser den, retter seg og prøver igjen.

+
+
+
context-budget-advisor.sh
+

Leser context-forbruket ved hver stopp, ber agenten runde av eller ta handoff ved 50 % og 75 % av vinduet.

+
+

Gjenbruk i Copilot:

+
"command": "bash ~/.claude/hooks/tiering-guard.sh"
+

Scriptet er det samme; feltnavnene inn/ut må matche verktøyet.

+
+
+
+
tiering-guard.sh · utdragClaude Code-format
+
deny() {
+  jq -n --arg r "$1" '{hookSpecificOutput:
+    {hookEventName:"PreToolUse",
+     permissionDecision:"deny",
+     permissionDecisionReason:$r}}'
+  exit 0
+}
+model=$(printf '%s' "$input" | jq -r '.tool_input.model // empty')
+[ -n "$model" ] && exit 0
+deny "no explicit model on this dispatch -- tier it and re-issue"
+
+
+
+ +
+ + +
+
+

Gi agenten sensorer

+
+
+ guides→ + agent loop + ←sensors +
+
+
+

Guides styrer førnivå 1

+

Instructions, skills: øker kvaliteten på første forsøk

+
+
+

Sensors fanger opp etternivå 2

+

Tester, lint, typecheck via hooks: agenten retter seg selv

+
+
+
Dårlig output er ofte en manglende sensor.
+
+ +
+ + +
+

Oppsummert

+
+ AGENTS.md + .github/skills/ + .github/agents/ + .github/hooks/ +
+

Versjonert, PR-bart, delt: én person bygger, alle får nytte.

+

Åpen standard, aug 2026: Agent Plugins 1.0 · AWS · Anysphere · Microsoft · OpenAI · Vercel

+
agent = modell + harness
+
Spørsmål?
+ +
+ +
+
+ +
+
+
+ + + + diff --git a/tests/unit/harness-deck.test.ts b/tests/unit/harness-deck.test.ts new file mode 100644 index 0000000..74a95b4 --- /dev/null +++ b/tests/unit/harness-deck.test.ts @@ -0,0 +1,58 @@ +import { describe, it, expect } from 'vitest'; +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; + +import sitemap from '@/app/sitemap'; +import nextConfig from '../../next.config.js'; + +const repoRoot = join(__dirname, '..', '..'); +const deck = readFileSync(join(repoRoot, 'public/harness.html'), 'utf8'); + +// The talk deck at /harness is unlisted, not private: nothing links to it, but +// the site's robots.txt allows the whole origin, so a crawler that ever learns +// the URL (a shared link, a referrer header) would index it and put a Norwegian +// slide deck in the results for a name the homepage is tuned to own. "Unlisted" +// has to be enforced on the page itself — listing /harness in robots.txt would +// advertise the exact URL it is meant to keep quiet. +describe('unlisted talk deck', () => { + it('carries a noindex robots directive', () => { + expect(deck).toMatch(/ { + const urls = sitemap().map((e) => e.url); + + expect(urls.some((u) => u.includes('harness'))).toBe(false); + }); + + it('is reachable at /harness via a rewrite to the static file', async () => { + // `rewrites` is optional on NextConfig, so losing the whole hook is a way + // this could break that a lookup in its result would not catch. + const { rewrites } = nextConfig; + if (!rewrites) throw new Error('next.config.js no longer defines rewrites()'); + + expect(await rewrites()).toContainEqual({ + source: '/harness', + destination: '/harness.html', + }); + }); +}); + +// The deck is presented from a laptop in a meeting room, where the wifi is +// someone else's problem and a blocked CDN means a broken slide mid-talk. It is +// one file with everything inlined, and that is a property worth holding: any +// external