Niles CRM artwork
Expected Parrot · Local-first relationship operations

A CRM even Lionel Hutz can operate

Niles gives an agent a safe command layer for clients, conversations, promises, intake forms, and follow-up—without turning relationship history into an opaque SaaS database.

Package: nilesInterface: JSON CLIStorage: local-firstExample: Hutz Law

01 What Niles does

Lionel Hutz has prospects, clients, calls, promises, and a recurring problem: none of them stay connected. Niles turns those fragments into an auditable workflow.

Remember relationships

Contacts, notes, traits, tags, and source history remain searchable.

Remember promises

Next steps become assigned, dated tasks rather than buried sentences.

Invite information

EDSL surveys support debriefs, human intake, and status requests.

Keep control

Responses and recommendations remain quarantined until reviewed.

The operating idea. Agents use commands, not storage files. Every command returns one versioned JSON envelope containing data, warnings, errors, and next steps.

02 The base data model

ObjectHutz Law exampleBehavior
ContactBurns IndustriesIdentity, company, cadence, traits, tags
NoteLiability callTimestamped interaction history
TaskSend engagement outlineAssigned, dated, stateful promise
SurveyClient debriefQuestions with deterministic routes
FormEP intake registrationRemote metadata recorded locally
SubmissionNew prospect answersQuarantined until review
RecommendationProposed follow-upBecomes a task only when accepted

Every mutation appends an event. The local index is rebuildable, making history, undo, integrity checks, and cloning share one source of truth.

03 Start the Hutz Law CRM

From an empty project directory, initialize Niles:

niles init

Then ask for state-aware operating guidance:

niles agent next
No storage ceremony. Niles manages the event log, index, surveys, and EP exchange artifacts. Users do not inspect or stage internal files.

04 Add clients and the people around them

Add Burns Industries as a prospective account:

niles contact add "Burns Industries" \
  --tag prospect \
  --trait priority=1 \
  --cadence-days 14

Add Smithers as the person Lionel speaks with:

niles contact add "Waylon Smithers" \
  --company "Burns Industries" \
  --role "Executive Assistant" \
  --email smithers@burns.example \
  --tag decision-maker

Inspect the account by its stable slug:

niles contact show burns-industries

05 Log an interaction, then debrief it

Record what happened on the call:

niles note add burns-industries \
  "Discussed workplace liability. Smithers requested an engagement outline." \
  --kind call

Preview a structured debrief before routing any answers:

niles survey run debrief \
  --contact burns-industries \
  --answers debrief-answers.json \
  --dry-run

Apply the same deterministic routes after reviewing the preview:

niles survey run debrief \
  --contact burns-industries \
  --answers debrief-answers.json

06 Turn promises into work

Create the promised follow-up:

niles task add burns-industries \
  "Send engagement outline" \
  --due 2026-09-10 \
  --assign lionel \
  --tag proposal

See Lionel’s open promises:

niles task list --assignee lionel

Close the loop when the outline is sent:

niles task done task_outline --note "Sent to Smithers"

07 Surface neglect before clients disappear

List relationships that have exceeded their cadence:

niles contact list --stale

Create a shareable local status view:

niles report status --html hutz-status.html

08 Find new clients with human intake

Niles · local

  • Exports surveys
  • Manages paths
  • Registers metadata
  • Imports to quarantine

EP · network

  • Publishes forms
  • Hosts respondents
  • Retrieves responses
  • Owns credentials

Export the intake survey locally:

niles intake export intake-basic

Run the returned data.publish_command with EP. Then register its output:

niles intake register intake-basic

Run the returned data.pull_command, then import responses:

niles intake import form_intake

Nothing changes a relationship until Lionel accepts, merges, or rejects:

niles intake review sub_mcbain --accept
Untrusted until reviewed. Import is idempotent and never creates or edits a client. The explicit review decision is the mutation gate.

09 Ask a known client what changed

Export a debrief for Burns Industries:

niles status-request export client-debrief

After EP publishes it, connect the registration to Burns and Smithers:

niles status-request register client-debrief \
  --contact burns-industries \
  --recipient smithers@burns.example

After running the returned pull command, import the local result:

niles status-request import form_status

Accept the routed update only after inspecting it:

niles status-request review sub_status --accept

10 Ask a model for the next relationship action

Package prospect context as an EDSL job:

niles recommend export next-steps --tag prospect

Run data.run_command with EP, then import the result:

niles recommend import --name next-steps

Only acceptance turns the proposal into a task:

niles recommend accept rec_followup \
  --assign lionel \
  --due 2026-09-15
Project context
Export job
Run with EP
Import quarantined
Accept as task
The review gate is structural. Importing a recommendation cannot create a task.

11 Search, inspect, and undo

Search across names, notes, traits, tags, and tasks:

niles search "workplace liability"

Inspect the relationship’s append-only history:

niles history --contact burns-industries --limit 20

Verify that durable history and its projection agree:

niles fsck

12 Sync and restore without bookkeeping

Each sync refreshes a human-readable CRM view in the repository README while preserving content outside Niles' marked section. Preview exactly what Niles would commit and push:

niles sync --dry-run

Commit durable CRM state and push it:

niles sync --message "Update Hutz Law CRM"

Create a portable archive when Git is not the transport:

niles export hutz-law.zip

13 Command map

GoalCommands
Orientagent next, status
Relationshipscontact add|show|list|update|status|tag|archive|merge
Worknote add|list, task add|list|update|done|reassign|cancel|suggest
Surveyssurvey list|show|copy|run|export-edsl, human-update --output
Editable workbooksheet export|import|review
Human formsintake export|register|import|status|review|close, status-request export|register|import|status|review
Model proposalsrecommend export|import|review|accept|reject
Audit and movesearch, history, undo, fsck, sync, export, import
A missing view is a feature gap. Do not bypass the command layer by reading internal storage. The JSON envelope is the supported agent interface.