jason-ai
CLAUDE.md4 starsChanged 11 days agoMITView on GitHub
Jason is a local-first, vendor-neutral SDR domain runtime: a .NET runtime process that owns durable campaign state and exposes a loopback Runtime API, a CLI that is a pure client of that API, JavaScript plugins that execute provider operations in a separate plugin-host process, and skills that teach AI agents the job. docs/architecture.md is the design authority for anything not covered here. - dotnet/ — all C#: Jason.slnx, src/Jason.App (the single executable; picks the mode from argv), src/Jason.Cli, src/Jason.Runtime, src/Jason.PluginHost,…
In this repository16 other files it gives its agents.
- approval-boundariesSkill
- audience-buildingSkill
- campaign-launchSkill
- campaign-planningSkill
- inbox-triageSkill
- linkedin-guardrailsSkill
- performance-analysisSkill
- sdr-operationsSkill
- sending-guardrailsSkill
- approvals-and-questionsSkill
- managed-campaign-workSkill
- operating-the-installationSkill
- planner-in-sessionSkill
- reporting-outside-effectsSkill
- researcher-in-sessionSkill
- troubleshooting-jasonSkill
The file
We split this CLAUDE.md into the rules it gives an agent
4 rules. About 0 are common, 2 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 36
Every child process is made by ChildProcess.StartInfo and started by ChildProcess.Start, with no console window of its own and no shell, …
- Very specific: line 112
Every package has a sha256: content digest that the child recomputes before it runs anything.
- Distinctive: line 253
Test-driven: write the failing test, make it pass, commit small.
- Distinctive: line 258
Commit messages and pull requests carry no ticket references, links to private systems, or tooling attribution trailers.
≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Working in this repository
Jason is a local-first, vendor-neutral SDR domain runtime: a .NET runtime process that owns durable
campaign state and exposes a loopback Runtime API, a CLI that is a pure client of that API,
JavaScript plugins that execute provider operations in a separate plugin-host process, and skills
that teach AI agents the job. docs/architecture.md is the design authority for anything not
covered here.
Layout
dotnet/— all C#:Jason.slnx,src/Jason.App(the single executable; picks the mode from argv),src/Jason.Cli,src/Jason.Runtime,src/Jason.PluginHost,src/Jason.Contracts,tests/(one xUnit project per source project).plugins/— plugin marketplace.skills/runtime/,skills/business/— skill packs.docs/— maintained documentation only.ui/— the browser UI: a Preact client of the Runtime API, built into the release assetjason-ui.zip;ui/README.mdsays how it is built and tested.LICENSE— MIT, for everything in the repository, the runtime included; every archive carries it asLICENSE, beside the executable andTHIRD-PARTY-NOTICES.global.jsonat the root pins the SDK; run every command from the repository root.
Commands
Building, testing, migrations, the UI's checks, and running a build of this checkout with its own data, skills and UI:
DEVELOPMENT.md.
Boundaries that the build enforces
Jason.Clireferences onlyJason.Contractsand the HTTP client. NeverJason.Runtime, EF Core or SQLite: the CLI never touches the database, it talks to the Runtime API.- Plugin JavaScript never runs inside the long-lived runtime process; it runs in the plugin-host mode
of the same executable.
Jason.Runtimereferences neither Jint norJason.PluginHost;Jason.PluginHostnever referencesJason.Runtime; manifests are read with YamlDotNet inJason.Runtimeonly, so the contracts, the CLI and the host stay YAML-free. - The dispatcher is deterministic runtime code; it never reads the meaning of a work item's context.
- Every child process is made by
ChildProcess.StartInfoand started byChildProcess.Start, with no console window of its own and no shell, and on Windows with the starting thread's error mode keeping the boxes the start itself raises, for a file that is no program image, off the desktop (StartErrorMode); a started child's own loader errors, a missing DLL say, follow the process error mode it inherits; a guard reads the compiled IL of every production assembly and refuses aProcessStartInfomade anywhere else, aProcess.Startanywhere else, a write toCreateNoWindoworUseShellExecute,Process.Startfrom a file name, and a read of a process'sStartInfo. Reflection, expression trees andnew T()are beyond what it reads. - Publication: this repository is public. Every file in it, every commit message and every pull request's title and
body is read publicly, so none of them carries a tracker key outside a branch name, an attribution trailer, a
pointer to an internal note, plan or system, a link to a private repository, a real person's name or account in the
text (git's author fields and CODEOWNERS aside), or a secret.
RepositoryHygieneTestsholds every file of the tree to the patterns a machine can check, and every link to a path of this repository to a file the tree holds; thehygiene.ymlworkflow holds each pull request's added lines, title and body to the same patterns. The rest is the author's to keep. - Package versions live in
dotnet/Directory.Packages.propsonly.THIRD-PARTY-NOTICEScovers every package ofJason.App's closure and the runtime packs the executable carries; after a package or SDK changeThirdPartyNoticesTestsis red untilpwsh .github/scripts/Write-ThirdPartyNotices.ps1writes it again. - Warnings are errors. Every test runs offline and uses an isolated temporary data directory.
- The UI is one more consumer of the Runtime API, and Jason stays command-line first and agent first. Three tests for
anything the UI needs from the runtime: it gets a CLI verb that makes sense typed by a person or an agent (the one door
outside the catalog is the pairing exchange
POST /ui/session); it returns facts, never views; the runtime never knows a screen exists — no route named after a page, no state, preference or cache kept for it, and with<data>/ui/absent the runtime is unchanged (UiGuardTests). A UI token has the run token's rights: one level of trust, the machine's user. The UI is tested in three layers (ui/README.md, "Testing"): unit tests of dense logic in Node, screen tests against the exchanges the runtime's own tests recorded underdocs/api/fixtures/(ApiFixtures, held one-to-one with the files and rewritten only underJASON_UPDATE_FIXTURES=1), and a browser smoke of the seams alone. Six guards hold the layers: the unit project runs without a DOM and may not import Preact; no.jsonunderui/but the configuration; a dependency allowlist; the fixture catalog; only the smoke's harness starts processes; one CI job runs all of it.
Conventions
- Terminology: plugin (not adapter), WorkItem (not Task), channels (not handles).
Skills, memory files, named preferences, campaign context, role notes and learned practice are six distinct
knowledge concepts; do not blur them. Membership in a Jason campaign is local;
campaign.enroll— a later, provider-side, approval-gated act — is a different thing. - Runtime API: RPC style,
POST /v1/{noun}.{verb}for everything, reads included. Success is 200 with a bare DTO; errors are{"error":{"code","message","retryable"}}with snake_case codes. JSON is snake_case, enums are snake_case strings, timestamps are ISO-8601 UTC, public ids are prefixed ULIDs (cmp_…,wi_…,att_…,rol_…). Partial patches distinguish absent from null (Optional<T>); lists paginate withlimit/cursor; every mutating request may carryactorandreason.campaign.createandworkitem.createalso take a caller'sidempotency_key, kept on the entity it created and never derived by the runtime or the CLI (docs/work-execution.md). - Work execution: work items move through one transition table (
WorkItemTransitions) and end through one routine (AttemptOutcomes); never write a status elsewhere. The attempt id is the fencing token: executor operations change state through a single guarded UPDATE, never read-then-write. The dispatcher claims insideBEGIN IMMEDIATE, at most as many items as it has free handler slots, one per campaign per scan. The launch envelope goes to the child on stdin; nothing goes on argv, and the capability token never reaches a child's environment, a work-directory file or an attempt row (redacted).docs/work-execution.mdis the contract. - Canonical operations: a contract is data, not code. One JSON document per operation lives under
docs/contracts/operations/and is embedded intoJason.Contractsas a resource, so the file a plugin author reads is the file the runtime enforces — never write a second copy of a rule that document already states. Schemas are the restricted JSON Schema dialectSchemaValidatorpublishes; a keyword outside that vocabulary is refused rather than ignored.context.inputis the reserved work-item context key carrying an operation's arguments: it is validated atworkitem.create, and again atworkitem.updatewhenever a patch names the key. The fixtures underdocs/contracts/fixtures/are executed by tests, so a document and the code cannot drift apart.docs/contracts/README.mdis the contract. The delivery contract,notification.send, is a family of its own underdocs/contracts/notifications/, embedded the same way and never in the operation catalog. - Routing: which plugin performs an operation for a campaign is a route, resolved by a pure function
over an immutable snapshot — campaign operation override, campaign default, global operation
override, global default, with exact masking and no fallback to another plugin. Routes are frozen
by the same reload that freezes plugins: the
Routessection ofsettings.jsonis read only there and at startup,route.set/route.unsetcopy the global half from the active snapshot, and a bad route rejects the whole reload. The claim is fail-closed: twenty checks in one published order, one code per reason, decided before a child exists, with the attempt kept. A binding names an account and never a credential. A provider identifier is pinned once and never overwritten — a different value is recorded beside it as a divergence and the attempt still succeeds. Provenance is written at the claim and completed at the end; nothing rewrites it.docs/routing.mdis the contract. - Plugins: a package is
~/.jason/plugins/<id>/withplugin.yaml,main.jsand optional modules; the directory name is the id. The manifest is validated against one fixed vocabulary of problem codes — package problems reject the whole reload and keep the previous snapshot, and so does a settings file the reload cannot read (settings_file_unreadable; one that is not there rejects nothing); the fourexecutable_*ones and, at a start only,probe_timed_outare environmental and only make that pluginunavailable. Every package has asha256:content digest that the child recomputes before it runs anything. The invocation goes to the child on stdin, exactly one outcome comes back on stdout, diagnostics are JSON Lines on stderr, and the exit code says only whether the protocol completed (0 outcome, 2 usage, 3 rejected, 4 host failure). Declaration is not permission: capabilities are requested by the manifest and granted underPlugins:Grants:<id>, resolved and frozen at each load; a manifest'sany_granted_hostlets a person grant a host it does not declare, and*still means the declared hosts alone. Configuration isPlugins:Config:<id>: text only, frozen at each load, handed on stdin and read withhost.config, a secret of the invocation, never in a package. A failure carries one of five classes —transientis retried,permanent,validationandblockedare final, andambiguoussays the provider may already have acted, so it is repeated only where the operation's own contract declares that repeating it is safe or that a recovery read makes it safe. An activated load journalsplugins_reloaded; there is noplugin.invokeoperation.docs/plugins.mdis the contract. - Inbound sync: a scan step between the lease enforcer and the summon reads each active campaign routed to a plugin
that reads conversations, and linked there, once per cadence — one
conversation.listwork item per page — and writes one reservedinbound_reply_observedline per thread that moved — identifiers, moments and the provider's flags, never a subject, a preview or a name — said once per campaign by a key. It interprets nothing; the line is a default manager trigger. A respond step between it and the summon reads those lines above a per-campaign watermark, whichever writer said them, and for a thread the campaign holds by its own pin queues aconversation.getand arespondercreated waiting on it — one open responder per thread, a further move patched onto itsinbound_reply— with identifiers and moments only, and never an idempotency key; itsconversation.listitems sit ten aboveResponder:Priority, so the sends wait and detection does not.docs/inbound-sync.mdis the contract. - Events: a third contract family,
events.readunderdocs/contracts/events/, embedded like the delivery contract and never in the operation catalog. A provider that declarescontracts.eventsreads an account by its own cursor, which the runtime stores per plugin and binding identity and never parses; a read pins nothing. What is read lands in an append-only store —event_sources,events,event_attributions, under the journal's three guards — each event once by a key per plugin, and an event no pin matches is kept. An event reaches a campaign only by the campaign identifier its operations pinned, decided once when it is stored, as a reserved line: a thread the other side moved asinbound_reply_observedunder inbound sync's key, every other kind asprovider_event_observed. A scan step between the lease enforcer and inbound sync hands one read at a time to a reader off the scan — never a work item or a handler slot — for every account a route binds, onDispatcher:EventsCadenceSeconds, thirty days deep, a span older than that said once asevents_gap_observed; inbound sync skips a campaign routed to such a plugin only where an event would reach it by the campaign-pin rule — one function, which the store attributes by — and only while that account's last read that succeeded started within the shorter of two cadences and the look-back. A person may ask for a span of an account to be read again (events.backfill: at most 366 days back, eight waiting an account), read only in a scan where every account is current, by a cursor of its own that never moves the account's;events.signalnames an account and nothing else and brings its next read forward, never within a minute of the last;events.sourcesis the accounts' record and never answers a cursor.docs/events.mdis the contract. - Notifications: a parked approval, a raised question, failed protective work, an account whose events were not
read, an open provider block and an item held at a daily limit of launches are notified as a pointer —
counts and where to look — by a scan step after the dispatch, delivered off the scan and recorded in a reserved
notification_sentline.docs/notifications.mdis the contract. - Projects and preferences: every campaign is in exactly one project — the default one, which every installation has,
unless it names another — and a project's slug is unique and never changes. The client is the installation and has
no entity; what a person states about it is a named preference, a fourth contract family under
docs/contracts/preferences/embedded like the others and never in the operation catalog: one value per key for the installation, checked against the key's schema, set and unset by a named person only, and journaled by its size and hash, never its value. The client, each project and each campaign have a folder under the harness root, made at the first create that needs it, with a marker naming its level and id and the person's memory file,AGENTS.md, beside a one-line import file for every host that reads a file of another name (CLAUDE.mdfor Claude Code); the runtime writes structure only — never a word of a memory file, never a delete, nevergit— and moves a campaign's folder by one rename when the campaign changes project, or refuses the move. A path is recorded on the entity; a folder is known by its marker, never its name, and a folder the person moves is recorded again only byproject.link, which reads the project's folder and each folder directly in itscampaigns/and never takes a campaign's project from a folder. A verb that takes a project and is given none takes it from the nearest marker above the folder the CLI runs in, when that folder is the one recorded, and says so on stderr; never a campaign.docs/projects.mdis the contract. - CLI: prints the exact API response as compact JSON on stdout by default,
--humanrenders for people, stderr is diagnostics only. Exit codes: 0 success, 1 understood and refused (an API business error, or a local act the CLI refused to perform), 2 usage error, 3 runtime unreachable or unauthorized. The verb map is one-to-one with API operations with nine declared exceptions:jason statusanswers "can this installation start work?", half of which is not the runtime's to know, so no operation can stand behind it;jason uninstalltakes this installation off the machine — a logon registration, an executable, a PATH entry, the files a deployment recorded — which the runtime neither performs nor is asked about; theskillsandupdatenouns and thejason ui installandjason plugin installverbs change this installation's files — deploying skills, replacing the executable and redeploying the recorded skills, placing the release's UI as the default build, placing a plugin package the release publishes — rather than ask the runtime anything (each is the one such verb under a noun whose other verbs are operations,plugin installasks only the reloadplugin reloadasks and never starts a runtime, and the verb-map tests hold theuigroup to its three operations andinstalland theplugingroup to its two andinstall);jason logsreads the installation's log files, which the runtime writes and is never asked about;jason session openstarts the person's own agent host — the one the named preferenceagent.interactive_hostnames — in a new terminal window at the campaign's folder with the item's brief as its first input, a terminal being no runtime's to start or know about, so the runtime is asked only whatapproval get --promptanddecision get --promptask andpreference.list, and Jason's brief asks the agent to propose only and not to approve, reject or answer; what it may run is the host's own permission settings' to decide (the verb-map tests hold thesessionnoun toopenalone); andjason doctorcollects one installation into one file, half of which is not the runtime's to know — of the runtime it asks only what the runtime says about itself, its roles, profiles, routes and subscriptions, and the journal and work-item pages it counts, keeping their metadata and dropping their values.status,logsanddoctorexit 0 or 1 and never 3 —logsnever asks the runtime, and for the other two "I could not ask the runtime" is an answer rather than a reason there is none — and the body ofstatuscarriesreadyso an agent branches on the body, never on the code.uninstallremoves skills only by receipt and the rest of the installation by rule, and keeps the data directory unless--purge-datasays otherwise.doctorlists what it would write before it writes —--dry-runlists it and writes nothing — reaches no network and uploads nothing; only--include-journaladds what the journal entries say: campaign names and fields as they changed, project names as they changed, the folders a project link recorded, campaign and work-item contexts as they changed, result formats, the addresses and numbers put on or taken off the do-not-contact register, the accounts a campaign's routes bind, the options decisions were answered with, the errors attempts ended with, provider identifiers, the verdicts roles recorded on replies and the decisions whose answers they carry, and the standing approvals people set: their categories, reactions, goals and limits, the effect, tool and operation a report names, the failure text and receipts notification channels reported, entries people and agents wrote themselves, the names actors gave, and the reasons they typed — and it prints a warning; a plugin's configuration underPlugins:Configtravels under no flag, withheld whole, keys and values; the runtime's own report (system_info) travels projected, withholding channel detail, presence detail outside the runtime's own words, skill problem text and the release-notes URL, and the bundle says so; and before anything is written the home directory becomes~, a data directory other than~/.jason<data>wherever it stands, a different one the runtime keeps its data in a mark of its own, the harness root<harness_root>wherever it stands, the executable's folder outside home<install_dir>unless a package manager's prefix holds it, the account's user, machine and domain names and SIDs marks of their own, every plugin id, role name and skill pack name Jason did not ship an alias per distinct name (<plugin-N>,<role-N>,<skill-pack-N>), and a skill source other than the public repository, with its ref unless that is a release tag,<skill-source>.CliAppstates all nine where the rule is written, each verb's--helppublishes its own contract — which checks are required, what is removed, what is never touched and what a bundle never carries — and guards hold the list to these nine. - Persistence: EF Core over SQLite in WAL mode, snake_case tables and columns, integer primary keys
plus a unique public ULID. The
journaltable is append-only, enforced three times over: no API path that changes an entry, an EF interceptor, and database triggers. Schema-less JSON — campaign context, contact custom fields, channel data — is stored as JSON text through a converter, and channels live incontact_channels. Migrations apply automatically on runtime start after a pre-migration backup; a migration that fails puts that backup back and the start fails saying so, and older backups are pruned only after a migration has succeeded. Never edit a migration after it has been committed; raw SQL such as the journal triggers is added to a new migration when it is created.work_items.result_jsonis frozen by a trigger once the item is finished; a filtered unique index allows one live attempt per item. - Configuration: strongly typed options only; user overrides in
~/.jason/config/settings.json, environment variablesJASON_*; the data directory itself comes fromJASON_DATA_DIRor defaults to~/.jason, and the harness root fromHarness:Rootor defaults to~/Jason— an absolute path, never the home directory, a folder holding it or the top of a drive, nor the data directory, a folder inside it or one holding it, read at every create and move; a test runtime is given a home of its own, so no test makes a folder in a real one. Settings are bound throughIOptions<T>with validation on start; the dispatcher readsIOptionsMonitor<T>each tick, so edits tosettings.jsonapply live exceptDispatcher:MaxParallel, which sizes the handler pool when the runtime starts, andDispatcher:Enabled, which decides at start whether this runtime has a dispatch loop at all. A live edit never stops the runtime: a refused edit keeps the last good settings and is logged by path and type, and a start with a malformed or mistyped file still fails, saying where — on stderr underjason runtime run, and after "It said:" in whatjason runtime startanswers: the line and byte of a malformed file, the setting's path and expected type for a mistyped one. - Logs are JSON Lines under
~/.jason/logs/and never contain request bodies, prompts, work-item contexts or the capability token: no text a person gives Jason — a context, a field, a brief, an operation's argument, a package's manifest, a refused setting, a reason — reaches them, held by a guard over every message template the runtime writes and by a run of the real executable that reads its own log back; the paths the installation runs from do, and a bundle rewrites them.jason logsreads them without the runtime;jason doctorcarries a bounded slice of them. - Everything in the repository is written in English.
Working style
- Test-driven: write the failing test, make it pass, commit small. Every behaviour arrives with its test.
- Feature branches; one squash-merged pull request per increment, described for an outside reader: what changed, why, how it was verified.
- Design notes, plans and scratch files do not belong in the repository.
- Commit messages and pull requests carry no ticket references, links to private systems, or tooling attribution trailers.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

