factor

module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 12, 2026 License: MIT

README

Factor

A fast, reliable, lightweight desktop AI agent and companion — with a real memory.

Factor is a single static Go binary that lives on your machine, talks to you over the CLI or Telegram, does real work with real tools, and remembers what matters across conversations. Its long-term memory is smrti — an AtomSpace-inspired engine with Bayesian truth values, attention economics, and emotional valence — so Factor doesn't just log what you said: it consolidates, prioritizes, and never repeats a critical mistake.

Factor distills the architecture of PicoClaw (bus + bounded workers, mid-turn steering, narrow pluggable seams, CGO-free portability) into a small, sharply focused codebase built for low-resource Linux desktops — it runs happily on an old Puppy Linux box.

Highlights

  • smrti memory as the soul — every exchange is stored as episodes; every turn recalls salience-ranked memories into context. Past failures surface as hard behavioral constraints (YOU MUST NOT …), background facts as notes. Consolidation (decay, promotion, contradiction resolution, pruning) runs inside smrti's reflect epochs. The agent also has deliberate remember / recall / forget / reflect tools.
  • Never keeps you waiting — long work runs through a background job engine (job_start, shell commands or delegated agent sub-tasks). The agent acks immediately; when a job finishes, the completion event re-enters the same session and Factor proactively messages you with the result — even mid-conversation.
  • One turn per session, steering for the rest — a second message during a live turn is injected into that turn between tool iterations instead of queuing.
  • Provider failover that works — OpenAI-compatible (OpenRouter, Ollama, LM Studio, Groq, llama.cpp, …) and native Anthropic backends, error classification, per-candidate cooldowns, context-overflow-triggered compaction at turn-safe boundaries.
  • Reasoning, dialect-translated — one provider.reasoning setting (effort or an explicit token budget) reaches OpenRouter as reasoning, OpenAI/Groq as reasoning_effort, and Anthropic as a thinking budget. Defaults to xhigh.
  • Hands on your desktopwindow_list / window_control (focus, close, move, maximize…), screenshot, mouse, type_text, press_key, clipboard, notify, open, desktop_info. X11 and Wayland via the usual helpers (xdotool, wmctrl, scrot, xclip, grim, wl-clipboard), macOS via osascript/screencapture, Windows via PowerShell. Registered automatically when a graphical session exists, skipped on headless boxes.
  • A real browser, not just fetch — CDP tools (browser_navigate/read/click/fill/ screenshot/eval/back) that attach to your running Chrome/Chromium/Brave DevTools port, or launch a managed instance — visible by default so you can watch it work.
  • Extensible everything:
    • Connectors: self-registering channel factories (Telegram included; WhatsApp, Twilio, Slack… are one package each).
    • Tools: one interface, one registration line; users curate the arsenal via tools.disabled.
    • MCP: built-in stdio client; mcp_add mounts any MCP server's tools at runtime and persists it to config.
    • Skills: markdown skills with progressive disclosure (catalog in prompt, full text on demand), skill_install from git or local dirs.
    • Instructions: AGENT.md / SOUL.md / USER.md plus drop-in workspace/instructions/*.md.
  • Self-managingconfig_get / config_set (redacted reads, schema-validated persisted writes), pkg_install (apt/apk/dnf/pacman/xbps/pkg/pip/pipx/uv/npm), cron schedules, HEARTBEAT.md proactive checks that cost zero LLM calls when idle.
  • Safety rails — workspace-restricted file access with symlink-escape resolution, exec deny-patterns for catastrophic commands, sender allowlists, secrets scrubbed from every tool result. (Rails, not a sandbox — see Security.)

Install

go install github.com/cyqlelabs/factor/cmd/factor@latest
# or grab a release binary; linux-amd64 targets GOAMD64=v1 (no SSE4.2 needed)

factor init      # interactive setup wizard

factor init is a terminal wizard: pick a provider from a menu, paste a key, choose a model from the endpoint's live model list, set the reasoning effort, then memory, channels and tools — each step verified as you go (the provider with a real completion, the Telegram token with getMe).

It also installs smrti, Factor's memory engine, if it is missing — trying uv tool install, pipx, pip install --user (retrying with --break-system-packages on PEP-668 distros), and finally a private venv under ~/.factor/venv. Nothing needs root. Any later run installs it too if it went missing (memory.auto_install), and the wizard offers to install the desktop helpers your session lacks.

Scripting a machine? factor init -y takes the defaults and never prompts; --no-install keeps it from installing anything.

Quick start

export FACTOR_PROVIDER_API_KEY=sk-or-...   # OpenRouter by default
factor                                     # interactive chat
factor -m "what's on my disk?"             # one-shot
factor gateway                             # daemon: Telegram, cron, heartbeat, jobs
factor status                              # daemon / provider / memory health

Factor spawns and supervises smrti serve rest on localhost automatically (memory.mode: "sidecar"), restarts it with backoff if it dies, and degrades gracefully (empty recalls, dropped writes, clear health status) when it's down. Point memory.mode: "external" + memory.url at a shared smrti if you run one.

Configuration

~/.factor/config.json (all keys optional — defaults work). Environment overrides: FACTOR_HOME, FACTOR_PROVIDER_API_KEY, FACTOR_PROVIDER_MODEL, FACTOR_MEMORY_MODE, …

{
  "provider": {
    "type": "openrouter",                    // openrouter|openai|groq|ollama|lmstudio|llamacpp|anthropic|custom
    "api_key": "sk-or-...",
    "model": "google/gemini-3.1-pro-preview",
    "reasoning": { "effort": "xhigh" },      // or {"max_tokens": 12000}; "none" turns it off
    "fallbacks": [{ "type": "ollama", "model": "qwen3:8b" }]
  },
  "memory": {
    "mode": "sidecar",                       // sidecar | external | off
    "auto_install": true,                    // install smrti when it is missing
    "personality": "balanced",               // analytical | curious | empathetic | maverick | deterministic
    "space": "main"
  },
  "channels": {
    "telegram": { "token": "123:ABC", "allow_from": ["your-telegram-id"] }
  },
  "mcp": {
    "servers": { "github": { "command": "github-mcp-server", "args": ["stdio"] } }
  },
  "tools": { "disabled": [], "restrict_to_workspace": true },
  "desktop": { "enabled": null },            // null = on when a display exists
  "browser": { "enabled": true, "headless": false },
  "heartbeat": { "enabled": true, "interval_minutes": 30 }
}

The workspace (~/.factor/workspace) is the agent's home: AGENT.md, SOUL.md, USER.md shape its identity; HEARTBEAT.md lists proactive tasks; instructions/, skills/, sessions/, cron/ do what they say.

Extending Factor

A new connector is one package:

func init() {
    channel.Register("mychat", func(raw json.RawMessage, b *bus.MessageBus) (channel.Channel, error) {
        var cfg MyConfig
        _ = json.Unmarshal(raw, &cfg)          // your own config section
        return New(cfg, b), nil                // implement Name/Start/Stop/Send/MaxMessageLength
    })
}

A new tool implements four methods (Name, Description, Parameters, Execute) and registers with registry.Register(t) — or skip Go entirely and add an MCP server (mcp_add, or the mcp.servers config section) or a markdown skill (workspace/skills/<name>/SKILL.md).

Architecture

channels (telegram, cli, …)        smrti (Python sidecar, SQLite)
        │  ▲                                ▲  REST :8420
        ▼  │                                │
   message bus ──► agent loop ──► memory engine (recall → prompt, store ← turns)
        ▲          │    │ one turn per session; overflow = steering
        │          │    └► provider chain (failover, cooldowns, compaction)
        │          └────► tool registry
        │                  fs · exec · web · browser(CDP) · desktop(X11/Wayland/
        │                  macOS/Windows) · memory · jobs · cron
        │                  config · pkg · skills · MCP mounts
        └── job engine / cron / heartbeat re-enter the bus proactively

Security model

Factor is a personal agent, not a multi-tenant service. The guardrails (workspace restriction, exec deny-patterns, allowlists, secret redaction) protect against accidents and casual prompt-injection — they are not a security boundary. Run it under your own account for yourself; set channels.telegram.allow_from; keep restrict_to_workspace on unless you know why you're turning it off.

Development

make check        # gofmt + vet + race tests + coverage gate
make cover        # statement coverage, fails under 90%
make build        # local binary
make build-all    # release cross-compile (incl. GOAMD64=v1 for old x86-64)
make build-tiny   # -tags nobrowser: smallest binary

CI fails below 90% statement coverage. The suite runs against fakes — scripted providers, a fake smrti (spawned by re-execing the test binary, so the real sidecar supervision and env contract are exercised), a fake Telegram API, a fake MCP server over real stdio JSON-RPC, a scripted desktop where every helper command is asserted, and a daemon test that boots the gateway and shuts it down with SIGTERM — plus live tests, a real headless-Chrome browser run and a real desktop round-trip, that auto-skip when the machine cannot host them.

License

MIT © CyqleLabs

Directories

Path Synopsis
cmd
factor command
Command factor is a fast, reliable desktop AI agent and companion with smrti long-term memory.
Command factor is a fast, reliable desktop AI agent and companion with smrti long-term memory.
internal
agent
Package agent implements the turn loop: one live turn per session, mid-turn steering for overflow messages, bounded worker concurrency, and a system prompt assembled from identity, workspace bootstrap files, drop-in instructions, the skills catalog, and smrti memory recall.
Package agent implements the turn loop: one live turn per session, mid-turn steering for overflow messages, bounded worker concurrency, and a system prompt assembled from identity, workspace bootstrap files, drop-in instructions, the skills catalog, and smrti memory recall.
app
Package app is the composition root: it wires config, provider chain, smrti memory, tools, skills, sessions, and the agent loop into one unit shared by the CLI and the gateway daemon.
Package app is the composition root: it wires config, provider chain, smrti memory, tools, skills, sessions, and the agent loop into one unit shared by the CLI and the gateway daemon.
browser
Package browser gives the agent a real browser via the Chrome DevTools Protocol (chromedp): it attaches to the user's running Chrome/Chromium/ Brave when a DevTools port is open, otherwise launches a managed instance — visible by default, so the user can watch the agent work.
Package browser gives the agent a real browser via the Chrome DevTools Protocol (chromedp): it attaches to the user's running Chrome/Chromium/ Brave when a DevTools port is open, otherwise launches a managed instance — visible by default, so the user can watch the agent work.
bus
Package bus decouples channels from the agent loop with bounded queues.
Package bus decouples channels from the agent loop with bounded queues.
channel
Package channel defines the connector seam.
Package channel defines the connector seam.
channel/telegram
Package telegram is the reference connector: raw Bot API over HTTP long-polling, no SDK.
Package telegram is the reference connector: raw Bot API over HTTP long-polling, no SDK.
config
Package config loads, defaults, persists, and redacts Factor's configuration.
Package config loads, defaults, persists, and redacts Factor's configuration.
cron
Package cron schedules recurring agent tasks.
Package cron schedules recurring agent tasks.
desktop
Package desktop gives the agent hands on the graphical session: listing and controlling windows, taking screenshots, moving the mouse, typing, the clipboard, notifications, and opening files or URLs.
Package desktop gives the agent hands on the graphical session: listing and controlling windows, taking screenshots, moving the mouse, typing, the clipboard, notifications, and opening files or URLs.
gateway
Package gateway runs Factor as a daemon: channels, cron, heartbeat, background jobs, and a local health endpoint.
Package gateway runs Factor as a daemon: channels, cron, heartbeat, background jobs, and a local health endpoint.
heartbeat
Package heartbeat periodically checks HEARTBEAT.md for user-defined tasks.
Package heartbeat periodically checks HEARTBEAT.md for user-defined tasks.
jobs
Package jobs runs long work in the background so the agent can reply immediately and report back when the work finishes.
Package jobs runs long work in the background so the agent can reply immediately and report back when the work finishes.
mcp
Package mcp implements a minimal MCP client (JSON-RPC 2.0 over stdio, newline-delimited) so external MCP servers' tools mount directly into Factor's tool registry as <server>__<tool>.
Package mcp implements a minimal MCP client (JSON-RPC 2.0 over stdio, newline-delimited) so external MCP servers' tools mount directly into Factor's tool registry as <server>__<tool>.
memory
Package memory is the soul of Factor: long-term memory backed by smrti (github.com/cyqlelabs/smrti), an AtomSpace-inspired engine with Bayesian truth values, attention economics, and emotional valence, reached over a localhost REST sidecar.
Package memory is the soul of Factor: long-term memory backed by smrti (github.com/cyqlelabs/smrti), an AtomSpace-inspired engine with Bayesian truth values, attention economics, and emotional valence, reached over a localhost REST sidecar.
provider
Package provider defines the LLM provider seam: a minimal Chat interface, wire-format adapters (OpenAI-compatible, Anthropic), error classification, and a failover chain with per-candidate cooldowns.
Package provider defines the LLM provider seam: a minimal Chat interface, wire-format adapters (OpenAI-compatible, Anthropic), error classification, and a failover chain with per-candidate cooldowns.
session
Package session persists conversation history as append-only JSONL files with a meta sidecar carrying the summary and logical truncation offset.
Package session persists conversation history as append-only JSONL files with a meta sidecar carrying the summary and logical truncation offset.
skills
Package skills implements progressive-disclosure markdown skills: only name + description enter the system prompt; the model reads the full SKILL.md with read_file when it decides a skill applies.
Package skills implements progressive-disclosure markdown skills: only name + description enter the system prompt; the model reads the full SKILL.md with read_file when it decides a skill applies.
tools
Package tools defines the tool seam and the built-in arsenal.
Package tools defines the tool seam and the built-in arsenal.
tui
Package tui draws Factor's interactive chat.
Package tui draws Factor's interactive chat.
version
Package version holds build metadata injected at link time.
Package version holds build metadata injected at link time.
wizard
Package wizard is Factor's interactive setup: a terminal front-end for `factor init` that picks a provider and model (probing them live), installs the smrti memory engine, wires up channels, and checks the desktop tools.
Package wizard is Factor's interactive setup: a terminal front-end for `factor init` that picks a provider and model (probing them live), installs the smrti memory engine, wires up channels, and checks the desktop tools.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL