Plugins

Modelimplementaties, tools, runtime-skills, audit-bestemmingen, connectors en achtergronddiensten gebruiken één RuntimePlugin-protocol en één PluginHost per Harness. Dit zijn plugins in de code-architectuur, geen MCP-servers. Een plugin kan meerdere mogelijkheden bijdragen; het systeem kent geen elkaar uitsluitende plugincategorieën toe.

De agent-lus, Session, permissies en registers blijven in de engine. Bestaande declaratieve oharness.plugin.json-bundels blijven ondersteund en worden nooit automatisch als uitvoerbare modules geïmporteerd.

Snel aan de slag

Draai de demonstratie met vier plugins, zonder inloggegevens of netwerkaanroepen:

npm run demo:plugins
npm run test:plugins

De voorbeelden in script/fixtures/runtime-plugins.ts implementeren een gescript model, een lokale tool, een audit-observer en een gesimuleerde connector. Het zijn demonstraties. De gehoste Cloud-connector wordt buiten tijdelijke instanties standaard geregistreerd als connector-cloud. Hij registreert bij het opstarten twee tools zonder netwerktoegang; een ontbrekende login of serverconfiguratie wordt een toolfout, geen opstartfout. Zet hem uit met { "id": "connector-cloud", "enabled": false } in de runtimePlugins van plugins.json. Wie OHarness embedt, kan cloudConnectors: false zetten. De directe Composio-adapter blijft een opt-in-alternatief voor ontwikkelaars; zet hem niet aan in gehoste clients. Pipedream is niet geïmplementeerd.

De ingebouwde geheugenplugin, ID memory, beheert nu de geheugentool, de promptindex per beurt, de webpagina en de levenscyclus van de synchronisatie op de achtergrond. Hij is ook actief in tijdelijke Harness-instanties (net als het eerdere gedrag van lokaal geheugen). Zet hem uit in plugins.json om al deze bijdragen te verwijderen. Dat staat los van het uitzetten van cloudsynchronisatie terwijl je lokaal geheugen behoudt.

Een plugin schrijven

import type { RuntimePlugin } from "@oharness/engine";

export default {
  id: "hello-tools",
  version: "1.0.0",
  apiVersion: 1,
  parseConfig(raw) {
    if (raw !== undefined) throw new Error("This plugin takes no options");
    return {};
  },
  setup(ctx) {
    ctx.tools.register({
      name: "hello_greet",
      description: "Return a greeting",
      inputSchema: { type: "object", properties: {} },
      readOnly: true,
      async execute() {
        return { content: [{ type: "text", text: "Hello" }] };
      },
    });
  },
} satisfies RuntimePlugin;

setup draait één keer per Harness-instantie. Houd clients, timers en veranderlijke status daarbinnen, niet op moduleniveau. Plugin-ID's bestaan uit kleine letters, cijfers, punten, underscores of koppeltekens. API-versie 1 wordt los van de eigen versie van de plugin gecontroleerd.

Laden en configureren

Een app die OHarness embedt, kan expliciet vertrouwde plugindefinities meegeven:

import { Harness } from "@oharness/engine";
import greeting from "./hello-plugin.js";

const harness = await Harness.create({
  runtimePlugins: [{ plugin: greeting, required: true }],
});
try {
  console.log(harness.runtimePluginStatus());
  // Create sessions / agents as usual.
} finally {
  await harness.close();
}

Je kunt ook runtimePlugins toevoegen aan het pluginbestand van de gebruiker (plugins.json in OHARNESS_HOME, normaal ~/.oharness). Een absoluut modulepad of een geïnstalleerd pakket dat vanuit die map te vinden is, wordt geaccepteerd; er wordt niets gedownload. Modules moeten een plugindefinitie als default exporteren. Gebruik voor distributie bij voorkeur gebouwde JavaScript; TypeScript als broncode vereist de subset van type-stripping die Node ondersteunt.

{
  "runtimePlugins": [
    {
      "id": "hello-tools",
      "module": "/absolute/path/to/hello-plugin.js",
      "enabled": true,
      "required": true
    }
  ]
}

config wordt doorgegeven aan parseConfig. Een geconfigureerde vermelding met dezelfde ID kan een inline plugin configureren of uitzetten, maar de implementatie niet vervangen door een andere module. Dubbele ID's binnen een van beide lijsten worden geweigerd. enabled: false slaat het importeren van de module, het parsen van de configuratie en de setup volledig over. required is standaard true; optionele fouten zijn zichtbaar in de status zonder dat het aanmaken van het Harness wordt afgebroken. De configuratielijst vervangt een eerdere lijst in plaats van pluginopties diep samen te voegen. Wijzigingen gelden bij het volgende aanmaken van een Harness, niet voor actieve sessies.

Projectconfiguratie kan geen runtime-plugins toevoegen of herconfigureren. HarnessOptions.config meegeven is een expliciete vertrouwensbeslissing van de embeddende applicatie. Zet geen secrets in projectconfiguratie of pluginmetadata. Laad ze via je goedgekeurde integratie voor inloggegevens of de omgeving en log ze nooit.

Interfaces voor mogelijkheden

InterfaceBijdrage
ctx.tools.register(tool)Tooldefinitie en -implementatie; de bestaande permissiepijplijn blijft gelden
ctx.skills.register(skill)Skill in het geheugen, beschikbaar zolang deze plugin leeft; dubbele namen worden geweigerd
ctx.models.register(provider, models)Modelimplementatie plus de bijbehorende modeldefinities, atomair gevalideerd
ctx.auth.register(provider)Authenticatie-implementatie via het bestaande AuthProvider-contract
ctx.hooks.on(event, handler)Bestaande getypeerde lifecycle-hook, met automatisch afmelden
ctx.audit.registerSink(sink)Best-effort-observer van permissie- en uitvoeringsevents
ctx.prompts.register(source)Asynchroon deel van de systeemprompt, ververst bij elke agent-beurt
ctx.pages.register(page)Paginadescriptor die de host rendert, plus lees- en actiehandlers
ctx.onStart(fn)Achtergrondwerk, gestart nadat alle plugin-setups klaar zijn
ctx.onDispose(fn)Te sluiten resources, in omgekeerde volgorde van verkrijgen

Alle bijdragen moeten tijdens setup worden geregistreerd. De host houdt ze bij en draait ze automatisch terug. Standaardregistratie weigert conflicten; plugins kunnen ingebouwde onderdelen of elkaars registraties niet stilletjes vervangen. Bestaande fluent register-API's blijven compatibel. Een auth-implementatie verwijderen wist geen inloggegevens. Modelaanbieders horen hun eigen ID's bij te dragen en niet te proberen bestaande catalogusvermeldingen te overschrijven. Kies een pluginmodel via de gewone model-configuratie of de API voor modelselectie.

Een runtime-skill levert name, description en body, plus optioneel een absolute directory als de body naar scripts of andere bestanden verwijst. Hij gebruikt dezelfde skill-tool met progressive disclosure als opgeslagen skills, verschijnt met bron runtime en wordt automatisch verwijderd als zijn plugin stopt. Runtime-skills kunnen geen ingebouwde, gebruikers-, project-, geconfigureerde of andere runtime-skills met dezelfde naam vervangen.

ctx.signal kondigt het afsluiten van de plugin aan. Tools en modelstreams krijgen ook het gecombineerde annuleringssignaal van host en aanroep. Houd achtergrondwerk coöperatief; registreer het opruimen van een resource direct na het verkrijgen ervan, vóór latere awaits die kunnen mislukken. onStart moet resolven nadat het werk is gestart, niet wachten op een oneindige worker-lus. Bewaar de completion-promise en wacht daarop in onDispose.

Auditplugin

De engine levert jsonlAuditPlugin (id: "audit-jsonl") mee, op basis van hetzelfde contract. Geef { path: "/absolute/path/to/events.jsonl" } mee als configuratie, via de embedding-API of via een modulevermelding die naar packages/engine/dist/runtime-plugins/builtin/jsonl-audit.js wijst. Hij maakt bovenliggende mappen aan, opent in append-modus (0600 voor een nieuw aangemaakt bestand) en leegt zijn wachtrij voordat hij sluit. Hij roteert geen bestanden en wijzigt geen rechten op bestaande bestanden.

Een audit-bestemming ontvangt RuntimeAuditEvent:

  • permission: bestaande beslissings- en antwoordrecords van de PermissionEngine, inclusief beslissingen van subagents die de host doorstuurt.
  • tool_execution: voltooide aanroepen, inclusief opgeworpen toolfouten die als foutresultaat zijn omgezet; bevat timing, de identiteit van de tool en welke runtime-plugin de eigenaar is.

Uitvoeringsevents laten invoer- en uitvoerbody's en inloggegevens weg. Permissierecords behouden de bestaande velden voor regel/principal, die paden of commando-argumenten kunnen bevatten: bestemmingen moeten met die gevoeligheid omgaan. Dit is geen volledige replay van events of compliancejournaal; aanroepen die vóór de PermissionEngine worden tegengehouden, krijgen in deze implementatie geen extra beslissingsrecords.

Elke bestemming krijgt een eigen snapshot en geordende wachtrij. Een bestemming die een fout opwerpt, blokkeert geen andere bestemming en verandert geen toolbeslissing. Wachtrijen zijn begrensd op 1.000 openstaande events per bestemming en waarschuwen bij en laten overtollige events vallen. Bij het afsluiten worden de wachtrijen geleegd voordat de resources van de bestemmingen sluiten, binnen de time-out voor opruimen. audit: false zet zowel runtime- auditevents als het bestaande permissiespoor uit.

Voorbeeld van een bestandsbestemming (binnen een plugin):

const file = await open(config.path, "a", 0o600); // node:fs/promises
ctx.onDispose(() => file.close());
ctx.audit.registerSink(async (event) => {
  await file.appendFile(JSON.stringify(event) + "\n");
});

Dit behoudt de bestaande best-effort-auditsemantiek van de engine. Een auditplugin verplicht maken garandeert het opstarten, niet fail-closed schrijven. Verplichte duurzame audit zou een apart ontworpen uitvoeringsbeleid vragen waarop wordt gewacht.

Connectorplugin

Gebruik createConnectorPlugin als handige factory, of implementeer RuntimePlugin direct. De factory geeft een gewone RuntimePlugin terug, geen ander mechanisme:

const plugin = createConnectorPlugin({
  id: "my-connector",
  version: "1.0.0",
  parseConfig: validateConnectorConfig,
  async connect(config, { signal, logger }) {
    // Wrap your selected SDK. Bind a trusted account here, not from model input.
    return createMyConnectorClient(config, { signal, logger });
  },
});

ConnectorClient biedt tools(): Promise<readonly Tool[]> en close(). Mislukt het vinden van tools, dan wordt de gekoppelde client toch gesloten. De helper zet readOnly en sessionLocal geforceerd uit voor externe tools, zodat metadata van de leverancier goedkeuring of de beperkingen van de plan-modus niet stilletjes kan omzeilen. Stel alleen goedgekeurde tools beschikbaar, gebruik namespaces, valideer argumenten en geef het signaal van elke aanroep door aan de SDK. Stuur niet een hele catalogus van de leverancier mee in elke modelprompt.

De host implementeert geen OAuth van leveranciers, accountkoppeling, webhooks of tenantopslag. De Cloud-connector levert het aanmaken van gehoste links, Web-interactie en geverifieerde koppeling; hij verandert het hostprotocol niet. Accountidentiteit bij meerdere gebruikers moet uit vertrouwde applicatiecontext komen, nooit uit een account-ID die het model kiest. Voor de pluginhost zelf zijn geen wijzigingen aan de cloudinfrastructuur nodig.

Levenscyclus en compatibiliteit

  1. Ingeschakelde vermeldingen bepalen en het contract en de configuratie valideren.
  2. Setups in de geconfigureerde volgorde draaien; registraties en eigendom van resources vastleggen.
  3. Starts draaien zodra alle setups klaar zijn; de actieve status beschikbaar maken.
  4. Bij een fout de mislukte plugin terugdraaien; als hij verplicht is, alles afbreken en terugdraaien.
  5. Bij het sluiten van het Harness nieuwe host-runs tegenhouden, bestaande runs annuleren/leeglopen, pluginaanroepen afbreken, bijdragen afmelden en daarna resources in omgekeerde volgorde sluiten.

Opruimen is idempotent en gaat door na afzonderlijke fouten. Het leeglopen van agents en het leeglopen/opruimen van elke plugin zijn begrensd op vijf seconden. Time-outs worden gemeld; ze kunnen niet-coöperatieve JavaScript niet geforceerd stoppen, en er kan na een time-out nog werk doorlopen. Een verouderde gekloonde plugintool weigert aanroepen nadat zijn plugin is gestopt.

CLI /plugins en Web /context tonen de runtimestatus naast declaratieve bundels. Foutmeldingen laten exceptieberichten van plugins weg, zodat er geen config-secrets uitlekken. Voeg zo nodig opgeschoonde diagnostiek toe met de logger die bij de plugin hoort.

V1 heeft bewust geen live unload, geen graaf van afhankelijkheden of diensten tussen plugins, geen installer voor pakketten op afstand, geen sandbox, geen loader voor willekeurige frontendmodules en geen vervangbare Agent/Session-kern. Geïnstalleerde in-process-plugins hebben de bevoegdheden van het proces: de context-API is een grens voor onderhoudbaarheid, geen beveiligingssandbox.

Zie configuratiegrenzen om oude inline configuratie te migreren. Aparte pluginbestanden zijn vertrouwde gebruikersconfiguratie en worden nooit geladen uit een willekeurige projectmap.