Skip to content

Repository files navigation

Bob

Bring On Board. Office-agent shell — hire a Bob, give them a role, a name, a mailbox, a channel, and they show up to work.

Bob is a thin TypeScript shell on top of pi-coding-agent. Pi owns the agent loop, tools, and LLM provider abstraction. Bob adds the things that turn one terminal agent into a named office hire: identity (Ed25519), a TPS-mail inbox, a Discord listener that responds to mentions, cron scheduling for briefings and sweeps, and a per-agent launcher generated by bob onboard <name>.

Quick start

# Hire Pulse as an EA, route through exe.dev's LLM gateway:
bob onboard pulse --role ea --provider exe-dev-gateway --model claude-opus-4-7

bob onboard does four things:

  1. Scaffolds ~/agents/pulse/ — soul.md (from the role template), bob.yaml config, ed25519 keypair, per-agent pi config (auth + gateway routing), and an executable launcher at bin/pulse.
  2. Provisions the Flair identity — registers the Ed25519 public key as a Flair Agent record and writes the persona into Pulse's Flair soul, in that order. Without the Agent record the agent's signed memory/soul calls don't verify; without the soul entry its own bootstrap returns no persona. Both are part of "onboarded", not follow-up chores.
  3. Opens an interview — pi-coding-agent runs in interactive mode with a meta-system-prompt that frames the session as a hiring conversation. You shape the persona by talking; the agent writes the refined soul.md itself when you signal you're done. Bob mirrors the result back into Flair.
  4. Leaves you with a working agent. pulse "what should I know this morning?" starts a session. bob run pulse --model claude-sonnet-4-6 "draft today's brief" overrides the model for one call. bob serve pulse --discord --discord-token-file ~/.tps/secrets/pulse-token --discord-channels 123,456 keeps Pulse listening on Discord and responding to mentions.

Flair identity, and where the soul lives

Registering an Agent record writes to Flair's admin-only Agent table, so onboarding needs an admin credential for the target instance. Bob reads it from FLAIR_ADMIN_PASS in the environment, or from the 0600 ~/.flair/admin-pass file flair init writes — never from a command-line flag, because argv is world-readable and lands in shell history. If neither is available, bob onboard fails with the exact fix rather than leaving a keypair on disk with no identity behind it. --no-flair scaffolds an agent with no Flair identity at all; --flair-url points it at a hub instead of the local spoke.

The persona is mirrored one way: soul.md → Flair, at the points where bob is already authoring one (bob onboard, bob align). Flair is the source of truth for consumers — it's what bootstrap returns, what travels to another machine running that identity, and what federates. soul.md is the source of truth for authoring — the hiring interview and your editor both write it, and the launcher reads it locally so a Flair outage can never boot a persona-less agent. Launch itself never syncs, in either direction.

If the two diverge (you edited soul.md after onboarding, or something else wrote the soul), the local file wins — loudly and losslessly: bob saves Flair's copy to soul.flair.bak.md next to soul.md and warns, naming both. Nothing is resolved silently.

What's wired

Concern Owner
Agent loop, tools, model providers pi-coding-agent
Soul / persona Flair soul (canonical) ← mirrored from ~/agents/<name>/soul.md by bob onboard / bob align
Identity Ed25519 keypair + Flair Agent record, both provisioned at onboard (Bob)
Memory Flair
Inbound mail TPS mail consumer (Bob)
Discord Listener + reply via discord.js binding (Bob)
Cron Generated launcher invocations (Bob + system cron)
Tool allowlist Per-role template, passed through to pi

Commands

Command What it does
bob onboard <name> Scaffold + register the Flair identity + write its soul + hiring interview
bob align <name> Recurring drift check — refines persona, mirrors it back into Flair
bob run <name> [prompt] Run one session. --model X overrides per call; --interactive for a TUI
bob serve <name> Daemon: mail consumer + optional --discord listener
bob doctor <name> Health check (stubbed — coming with branch-office tooling)

Per-call model override is the lightweight version of dynamic routing — bake the right model into each cron command (opus for strategy, sonnet for briefings, kimi for digests) without standing up multiple agents.

Where Bob fits

Bob is for small teams who want a handful of named, role-specific agents — a strategist, an EA, a reviewer — and want them to show up in mail and chat as themselves.

Bob isn't a multi-agent framework (CrewAI, AutoGen, LangGraph), a memory layer (Letta, Honcho), or a general agent platform (Goose, Mastra, Google ADK). It's the office plumbing around an existing terminal agent.

Bob is one layer of an open stack:

  • pi-coding-agent — the agent loop, tools, and LLM provider abstraction Bob sits on top of.
  • Bob (you are here) — the office shell: identity, mailbox, channels, scheduling, doctor.
  • Flair — the memory layer Bob's agents talk to by default; orchestrator-agnostic, self-host, federates across hosts.
  • TPS CLI — the coordination layer Bob's bob serve mail consumer plugs into; mail, branch-office bring-up, agent-to-agent dispatch.

Each layer stands alone — use whichever fits your stack, swap out the others. Bob's value is concentrated at the office-shell layer; the rest is composable.

If you already use pi and want each agent to have a name, a key, a mailbox, and a channel — Bob is what gets you there.

Compatibility

  • Runs any pi extension, skill, prompt template, or theme. Bob's launcher is a thin wrapper around pi --provider ... --model ... --append-system-prompt .... Anything pi accepts, Bob's agents accept.
  • Memory via Flair, with bridges to others. Flair ships bridges to mem0, claude-project memory, ChatGPT, and more. A Bob agent can read memory from whichever layer your stack already uses.
  • LLM provider-neutral. Bob's bob.yaml picks; current production examples use exe.dev's VM-authenticated LLM gateway (anthropic via baseUrl override), but ollama-cloud, ollama-newton, anthropic direct, openai, and omlx all work.

Install

npx @tpsdev-ai/bob help          # try it
npm install -g @tpsdev-ai/bob    # keep it

One package. The CLI, the shell runtime, the role templates and every blessed capability ship together — there is nothing else to install to get a working agent, and no way to end up with half of one.

Repo layout

bin/bob                 the `bob` entry point
src/
  cli.ts                the `bob` command
  shell/                runtime + integrations — mail consumer, Discord bridge,
                        init, run, onboard/align, doctor, capability catalog
  capabilities/         the blessed capabilities, each a pi extension
    discord/            outbound tools + inbound gateway listener
    flair/              memory search/write/get over the agent's Flair store
    observatory/        team-view producer
    fixture/            a no-op capability that proves the loader end to end
roles/                  ea, writer, reviewer, coder, qa, custom
test/                   mirrors src/

A capability is a pi extension plus a manifest. Bob's catalog (src/shell/capability-catalog.ts) is the curation boundary: only blessed names may appear in an agent's bob.yaml capabilities:, and each one resolves through this package's own exports map, so a checkout and an install load the identical file.

Configuring a capability

Declare it in capabilities:, then give it a top-level block named after it. The block is validated against the capability's schema before its extension loads, so a bad block fails at startup with a message naming the problem rather than at the first tool call.

capabilities:
  - flair
  - observatory

flair:
  url: http://127.0.0.1:19926
  agentId: agent-one
  keyFile: ~/.flair/keys/agent-one.key

observatory:
  observatoryUrl: http://127.0.0.1:9926
  officeId: main-office
  officeKeyFile: ~/.flair/keys/office.key
  staleThresholdSeconds: 600
  agents:
    - agentId: agent-one
      name: Agent One
      role: Strategy
      heartbeatFile: ~/.bob/signals/agent-one.hb
    - agentId: agent-two
      role: Execution

bob.yaml is read by a small purpose-built reader, not a full YAML parser. Inside a capability block it supports scalars (name: value), inline lists (name: [a, b]), block lists of scalars, and block lists of one-level - name: value mappings — the shapes above. Anything beyond that (nested mappings, nested lists, {a: b} flow mappings, anchors, multi-line scalars) is rejected with the block name and line number rather than parsed into something that merely looks right. Values are coerced: true/false become booleans, whole numbers become numbers, and quoting forces a string — quote any id you need to stay textual, like a Discord snowflake.

Secrets never go in bob.yaml. Capability schemas take a pathkeyFile, officeKeyFile, tokenFile — and the value is read from that file at startup.

Status

0.x. The interactive onboard flow, real bob run, Discord listener with auto-reply, per-agent pi config seeding, role templates (ea/writer/reviewer/coder/qa/custom), and bob doctor all landed this week (PR-15 through PR-22). Branch-office docs and richer routing tables are next.

License

Apache-2.0 — matches the rest of tpsdev-ai.

About

Moldable office-agent shell. One shape, any role — identity, memory, mail, and channels wired; soul is yours.

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages