brainiac 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,14 +7,9 @@ CLI_PROVIDERS_DIR = File.join(BRAINIAC_DIR, "cli-providers")
7
7
 
8
8
  # --trust-all-tools alone doesn't bypass the non-interactive deny list in kiro-cli 1.29.8+.
9
9
  # Adding --trust-tools with explicit tool names ensures write/exec tools are approved.
10
+ # This is now configured in the kiro provider's default_args instead of being hardcoded here.
10
11
  TRUSTED_TOOLS = "execute_bash,fs_write,fs_read,code,grep,glob,web_search,web_fetch,use_subagent,use_aws"
11
12
 
12
- def add_trust_tools!(cmd, agent_cli_args)
13
- return if agent_cli_args.include?("--trust-tools")
14
-
15
- cmd.push("--trust-tools", TRUSTED_TOOLS)
16
- end
17
-
18
13
  # Clean up all worktrees associated with a card: the primary worktree and any
19
14
  # cross-agent review worktrees (e.g. glados-fizzy-123-*, threepio-fizzy-123-*).
20
15
  # Safe: skips worktrees with uncommitted changes.
@@ -49,24 +44,86 @@ def cleanup_card_worktrees(card_number, repo_path:, primary_worktree: nil, prima
49
44
  LOG.info "Card ##{card_number}: cleaned up #{cleaned} worktree(s)" if cleaned.positive?
50
45
  end
51
46
 
47
+ # Load a CLI provider config from ~/.brainiac/cli-providers/<name>.json.
48
+ # Returns a hash with normalized keys, or {} if not found.
49
+ def load_cli_provider(provider_name)
50
+ return {} unless provider_name
51
+
52
+ provider_file = File.join(CLI_PROVIDERS_DIR, "#{provider_name}.json")
53
+ return {} unless File.exist?(provider_file)
54
+
55
+ raw = JSON.parse(File.read(provider_file))
56
+ config = {
57
+ "agent_cli" => raw["binary"],
58
+ "agent_cli_args" => raw["default_args"],
59
+ "agent_model_flag" => raw["model_flag"],
60
+ "agent_effort_flag" => raw["effort_flag"],
61
+ "allowed_models" => raw["models"],
62
+ "allowed_efforts" => raw["efforts"]
63
+ }
64
+ # agent_flag: how the agent identity is passed (default: "--agent").
65
+ # Set to null/false in provider JSON to suppress passing agent name entirely.
66
+ # We must preserve the key even when nil so merges don't lose the "no agent flag" intent.
67
+ config["agent_flag"] = raw.key?("agent_flag") ? raw["agent_flag"] : "--agent"
68
+ # prompt_mode: "stdin" (default) or "flag" — how the prompt is delivered.
69
+ config["prompt_mode"] = raw["prompt_mode"] || "stdin"
70
+ config["prompt_flag"] = raw["prompt_flag"] if raw["prompt_flag"]
71
+ # resume_flag: when set, follow-up dispatches use this flag to continue the
72
+ # most recent session in the working directory (e.g. "-c" or "--continue").
73
+ config["resume_flag"] = raw["resume_flag"] if raw["resume_flag"]
74
+ # Compact nil values except agent_flag (which uses nil to mean "don't pass agent name")
75
+ agent_flag_value = config["agent_flag"]
76
+ config.compact!
77
+ config["agent_flag"] = agent_flag_value if raw.key?("agent_flag")
78
+ config
79
+ rescue JSON::ParserError => e
80
+ LOG.warn "Failed to parse CLI provider '#{provider_name}': #{e.message}"
81
+ {}
82
+ end
83
+
52
84
  # Resolve CLI config for a project by merging provider defaults with project overrides.
53
- # Priority: project-level keys > provider file > DEFAULT_PROJECT
54
- def resolve_project_cli_config(project_config)
55
- provider_config = {}
56
- if (provider_name = project_config["cli_provider"])
57
- provider_file = File.join(CLI_PROVIDERS_DIR, "#{provider_name}.json")
58
- if File.exist?(provider_file)
59
- raw = JSON.parse(File.read(provider_file))
60
- provider_config = {
61
- "agent_cli" => raw["binary"],
62
- "agent_cli_args" => raw["default_args"],
63
- "agent_model_flag" => raw["model_flag"],
64
- "allowed_models" => raw["models"]
65
- }
66
- end
85
+ # Priority: cli_provider_override > agent-level cli_provider > project-level cli_provider > DEFAULT_PROJECT
86
+ def resolve_project_cli_config(project_config, cli_provider_override: nil, agent_name: nil)
87
+ # Determine which CLI provider to use (priority: override > agent > project)
88
+ provider_name = cli_provider_override
89
+ provider_name ||= agent_cli_provider_for(agent_name) if agent_name
90
+ provider_name ||= project_config["cli_provider"]
91
+
92
+ provider_config = load_cli_provider(provider_name)
93
+
94
+ DEFAULT_PROJECT.merge(provider_config).merge(project_config).tap do |resolved|
95
+ # If an override or agent-level provider was used, it should win over the
96
+ # project-level cli_provider's config. Re-apply the override provider on top.
97
+ resolved.merge!(provider_config) if provider_name && provider_name != project_config["cli_provider"]
98
+ end
99
+ end
100
+
101
+ # Get the cli_provider configured at the agent level in agents.json.
102
+ def agent_cli_provider_for(agent_name)
103
+ return nil unless agent_name
104
+
105
+ key = agent_name.downcase.gsub(/[^a-z0-9-]/, "-")
106
+ entry = AGENT_REGISTRY[key]
107
+ return nil unless entry.is_a?(Hash)
108
+
109
+ entry["cli_provider"]
110
+ end
111
+
112
+ # Detect CLI provider override from inline [cli:X] tag or Fizzy card tags.
113
+ # Returns the provider name (e.g. "grok") or nil.
114
+ def detect_cli_provider(text: "", tags: [])
115
+ # Inline tag: [cli:grok]
116
+ if (match = text.match(/\[cli:(\w+)\]/i))
117
+ return match[1].downcase
67
118
  end
68
119
 
69
- DEFAULT_PROJECT.merge(provider_config).merge(project_config)
120
+ # Fizzy card tags: cli-grok
121
+ tags.each do |tag|
122
+ name = (tag.is_a?(Hash) ? tag["name"] : tag).to_s.downcase
123
+ return name.sub("cli-", "") if name.start_with?("cli-")
124
+ end
125
+
126
+ nil
70
127
  end
71
128
 
72
129
  # Copy gitignored files matching .worktreeinclude patterns from repo to worktree.
@@ -502,13 +559,17 @@ rescue StandardError => e
502
559
  end
503
560
 
504
561
  def run_agent(prompt, project_config:, chdir: nil, log_name: "agent", model: nil, effort: nil, agent_name: nil, card_number: nil, comment_id: nil,
505
- source: nil, source_context: {}, skip_column_move: false)
506
- resolved = resolve_project_cli_config(project_config)
562
+ source: nil, source_context: {}, skip_column_move: false, cli_provider: nil, resume: false)
563
+ resolved = resolve_project_cli_config(project_config, cli_provider_override: cli_provider, agent_name: agent_name)
507
564
  chdir ||= resolved["repo_path"]
508
565
  model ||= resolved["agent_model"]
509
566
  effort ||= resolved["agent_effort"]
510
567
  agent_config_name = agent_name&.downcase&.gsub(/[^a-z0-9-]/, "-")
511
568
 
569
+ # Auto-resume: if the provider supports session resume and we're in a worktree
570
+ # that has had a previous session, resume it. Only applies to follow-ups (not first dispatch).
571
+ should_resume = resume && resolved["resume_flag"]
572
+
512
573
  ensure_fizzy_yaml!(chdir, project_config)
513
574
  Thread.new { scrub_invalid_attachments!(chdir) }
514
575
 
@@ -517,24 +578,22 @@ def run_agent(prompt, project_config:, chdir: nil, log_name: "agent", model: nil
517
578
  FileUtils.mkdir_p(File.dirname(log_file))
518
579
 
519
580
  prompt_file = write_agent_prompt_file(prompt, log_name, timestamp)
520
- cmd = build_agent_cmd(resolved, agent_config_name: agent_config_name, model: model, effort: effort)
581
+ cmd = build_agent_cmd(resolved, agent_config_name: agent_config_name, model: model, effort: effort, prompt_file: prompt_file, resume: should_resume)
582
+ prompt_mode = resolved["prompt_mode"] || "stdin"
583
+
521
584
  spawn_env = agent_env_for(agent_name)
522
585
 
523
586
  LOG.info "Running #{resolved["agent_cli"]} in #{chdir}, logging to #{log_file}"
524
587
  LOG.info "Prompt written to #{prompt_file}"
525
- LOG.info "Command: #{cmd.join(" ")}"
588
+ LOG.info "Command: #{cmd.join(" ")}#{" (resuming session)" if should_resume}"
526
589
  LOG.info "Injecting #{spawn_env.size} env var(s) for agent #{agent_name}: #{spawn_env.keys.join(", ")}" unless spawn_env.empty?
527
590
 
528
- head_before = nil
529
591
  project_key_for_restart = PROJECTS.find { |_k, v| v == project_config }&.first
530
- if project_key_for_restart == "brainiac"
531
- head_before, = Open3.capture2("git", "rev-parse", "HEAD", chdir: chdir)
532
- head_before = head_before.strip
533
- end
592
+ head_before, status_before = capture_git_state(chdir) if project_key_for_restart == "brainiac"
534
593
 
535
594
  pid = spawn(spawn_env, *cmd,
536
595
  chdir: chdir,
537
- in: prompt_file,
596
+ **(prompt_mode == "stdin" ? { in: prompt_file } : {}),
538
597
  out: [log_file, "w"],
539
598
  err: %i[child out])
540
599
 
@@ -546,7 +605,8 @@ def run_agent(prompt, project_config:, chdir: nil, log_name: "agent", model: nil
546
605
  prompt_file: prompt_file, chdir: chdir, source: source,
547
606
  source_context: source_context, project_config: project_config,
548
607
  card_number: card_number, skip_column_move: skip_column_move,
549
- head_before: head_before, project_key_for_restart: project_key_for_restart
608
+ head_before: head_before, status_before: status_before,
609
+ project_key_for_restart: project_key_for_restart
550
610
  )
551
611
  end
552
612
 
@@ -578,13 +638,28 @@ def write_agent_prompt_file(prompt, log_name, timestamp)
578
638
  end
579
639
 
580
640
  # Build the CLI command array for an agent invocation.
581
- def build_agent_cmd(resolved, agent_config_name: nil, model: nil, effort: nil)
641
+ # When prompt_file is provided and prompt_mode is "flag", appends the prompt as a CLI argument.
642
+ # When resume is true and the provider has a resume_flag, adds it to continue the last session.
643
+ def build_agent_cmd(resolved, agent_config_name: nil, model: nil, effort: nil, prompt_file: nil, resume: false)
582
644
  cmd = [resolved["agent_cli"]]
583
- cmd.push("--agent", agent_config_name) if agent_config_name
645
+ # agent_flag controls how the agent identity is passed. Defaults to "--agent".
646
+ # Provider configs can set it to a different flag or null to suppress entirely.
647
+ agent_flag = resolved.key?("agent_flag") ? resolved["agent_flag"] : "--agent"
648
+ cmd.push(agent_flag, agent_config_name) if agent_flag && agent_config_name
584
649
  cmd.concat(resolved["agent_cli_args"].split)
585
- add_trust_tools!(cmd, resolved["agent_cli_args"])
586
- cmd.push(resolved["agent_model_flag"], model) if resolved["agent_model_flag"] && !resolved["agent_model_flag"].empty? && model
650
+ # Only pass --model if the model is a valid ID for this provider.
651
+ # "auto" means "let the CLI choose" skip passing it unless the provider explicitly maps it.
652
+ if model && resolved["agent_model_flag"] && !resolved["agent_model_flag"].empty?
653
+ allowed = resolved["allowed_models"] || {}
654
+ # Pass the model if it's a mapped value (e.g. "claude-opus-4.6") or the key itself is mapped
655
+ is_known = allowed.value?(model) || allowed.key?(model)
656
+ cmd.push(resolved["agent_model_flag"], model) if is_known
657
+ end
587
658
  cmd.push(resolved["agent_effort_flag"], effort) if resolved["agent_effort_flag"] && !resolved["agent_effort_flag"].empty? && effort
659
+ # Resume the most recent session in the working directory (for multi-turn CLIs like grok)
660
+ cmd.push(resolved["resume_flag"]) if resume && resolved["resume_flag"]
661
+ # prompt_mode: "flag" passes the prompt file path via the configured prompt_flag (e.g. --prompt-file).
662
+ cmd.push(resolved["prompt_flag"], prompt_file) if prompt_file && resolved["prompt_mode"] == "flag" && resolved["prompt_flag"]
588
663
  cmd
589
664
  end
590
665
 
@@ -621,7 +696,7 @@ def handle_agent_completion(**ctx)
621
696
  end
622
697
 
623
698
  brain_push(message: "#{ctx[:agent_config_name] || "agent"}: #{ctx[:log_name]}")
624
- check_brainiac_restart(ctx[:head_before], ctx[:chdir], ctx[:project_key_for_restart], ctx[:agent_config_name])
699
+ check_brainiac_restart(ctx[:head_before], ctx[:status_before], ctx[:chdir], ctx[:project_key_for_restart], ctx[:agent_config_name])
625
700
  end
626
701
 
627
702
  def handle_fizzy_post_session(fizzy_card, exit_status, signaled, agent_name, chdir, source, source_context, project_config, skip_column_move)
@@ -672,12 +747,21 @@ def handle_plan_finalization(prompt_file, agent_name, project_config)
672
747
  end
673
748
  end
674
749
 
675
- def check_brainiac_restart(head_before, chdir, project_key_for_restart, agent_config_name)
750
+ # Capture git HEAD and working tree status for a directory.
751
+ # Returns [head_sha, status_porcelain] or [nil, nil] on failure.
752
+ def capture_git_state(chdir)
753
+ head, = Open3.capture2("git", "rev-parse", "HEAD", chdir: chdir)
754
+ status, = Open3.capture2("git", "status", "--porcelain", chdir: chdir)
755
+ [head.strip, status.strip]
756
+ rescue StandardError
757
+ [nil, nil]
758
+ end
759
+
760
+ def check_brainiac_restart(head_before, status_before, chdir, project_key_for_restart, agent_config_name)
676
761
  return unless project_key_for_restart == "brainiac" && head_before
677
762
 
678
- head_after, = Open3.capture2("git", "rev-parse", "HEAD", chdir: chdir)
679
- git_status, = Open3.capture2("git", "status", "--porcelain", chdir: chdir)
680
- if head_after.strip != head_before || !git_status.strip.empty?
763
+ head_after, status_after = capture_git_state(chdir)
764
+ if head_after != head_before || status_after != (status_before || "")
681
765
  queue_brainiac_restart(agent_config_name || "agent")
682
766
  else
683
767
  LOG.info "[Brainiac] #{agent_config_name || "agent"} session on brainiac had no changes — skipping restart"
@@ -24,24 +24,13 @@ PROMPT_CORE = <<~PROMPT
24
24
  Memory files MAY exist at `{{MEMORY_DIR}}/` — this is inside the brain, so they survive worktree deletion.
25
25
 
26
26
  **At the very start of every session:**
27
- 1. Read `{{MEMORY_DIR}}/card-{{CARD_ID}}.md`. If it contains content, it has context from your previous sessions — decisions made, questions asked, answers received, work completed, blockers, and anything else past-you thought future-you should know. If the file is empty (first session on this card), just proceed without prior context.
27
+ 1. Read `{{MEMORY_DIR}}/card-{{CARD_ID}}.md`. If it contains content, it has context from your previous sessions. If the file is empty (first session on this card), just proceed without prior context.
28
28
 
29
- **Note:** Only the last 15 comments are included in card context (truncated to 500 chars each). Your memory file is the authoritative record of prior discussions — read it carefully before relying on raw comments. If you need the full text of a truncated comment, run: `fizzy comment show COMMENT_ID --card CARD_NUMBER`
29
+ **Note:** Only the last 15 comments are included in card context (truncated to 500 chars each). Your memory file is the authoritative record of prior discussions. If you need the full text of a truncated comment, run: `fizzy comment show COMMENT_ID --card CARD_NUMBER`
30
30
 
31
31
  **Before you finish every session (even if you didn't complete the task):**
32
- 2. Create or update your memory file at `{{MEMORY_DIR}}/card-{{CARD_ID}}.md`.
33
-
34
- Write in a format optimized for AI consumption. Include:
35
- - Current status of the task (not started / in progress / blocked / done)
36
- - What you accomplished this session
37
- - Key decisions made and why
38
- - Questions you asked and answers you received
39
- - Open questions still waiting for answers
40
- - Relevant file paths, branch state, PR URLs
41
- - Anything that would help a fresh instance of you pick up exactly where you left off
42
- - A brief timeline of sessions (append, don't overwrite previous entries)
43
- - The exact comment IDs you posted this session (so future sessions can detect duplicates)
44
- - A condensed summary of the full comment history (so future sessions don't need the raw comments — your memory is the authoritative record of what was discussed)
32
+ 2. Update your memory file at `{{MEMORY_DIR}}/card-{{CARD_ID}}.md`.
33
+ Write what future-you needs to pick up where you left off. Use your judgement on what's important — status, decisions, open questions, file paths, PR URLs, timeline of sessions.
45
34
 
46
35
  ## Brain (Long-Term Memory via qmd)
47
36
  You have a long-term memory called the "brain" that persists across ALL sessions and ALL cards.
@@ -63,17 +52,9 @@ PROMPT_CORE = <<~PROMPT
63
52
  Standard unix commands (cd, ls, grep, cat, git, curl, etc.) don't need a brain search.
64
53
  But for project-specific tools, do NOT guess at flags or syntax — wrong commands waste time and tokens. Look it up first.
65
54
 
66
- **When to save knowledge (be selective — NOT every card needs a knowledge entry):**
67
- - User explicitly asks you to remember something → save it
68
- - A significant architecture decision or convention is established document it
69
- - You discover a non-obvious gotcha that would bite future-you → record it
70
- - A major workflow or process changes → update the relevant doc
71
-
72
- **Do NOT save knowledge for:**
73
- - Routine card work (bug fixes, small features, standard implementations)
74
- - Things that are already documented in the codebase (READMEs, comments, etc.)
75
- - Minor corrections or one-off fixes
76
- - Information that's only relevant to the current card (that goes in memory, not knowledge)
55
+ **When to save knowledge:** Be selective — only save significant architecture decisions,
56
+ non-obvious gotchas, major workflow changes, or things the user explicitly asks you to remember.
57
+ Routine card work and things already documented in the codebase don't need brain entries.
77
58
 
78
59
  Organize files like:
79
60
  - `{{KNOWLEDGE_DIR}}/projects/marketplace.md`
@@ -100,32 +81,17 @@ PROMPT_CORE = <<~PROMPT
100
81
  - Brain knowledge (`{{KNOWLEDGE_DIR}}/`) = permanent technical knowledge (shared across all agents)
101
82
  - Brain persona (`{{PERSONA_DIR}}/`) = permanent communication style (yours only)
102
83
 
103
- ## Communication Rules (CRITICAL — duplicates waste everyone's time)
104
- You may only post **once per session** unless you are asking a distinct new question.
105
-
106
- Before posting ANY comment or response:
107
- 1. Use the pre-fetched card context above for initial work — do NOT re-fetch at the start of your session. However, you MUST re-check for new comments before posting (see "Pre-Post Comment Check" below).
108
- 2. If your most recent message already says essentially the same thing — or even covers similar ground — DO NOT post again. Just move on silently.
109
- 3. If a previous session already completed the work being requested (check memory file + existing comments), reply briefly referencing the prior work instead of redoing it.
110
- 4. Never post the same status update, summary, or question twice.
111
- 5. Combine all of your updates into a single message at the end of your work. Do NOT post incremental status updates (e.g. "looking into it", "starting work", "almost done"). One final summary is enough.
112
- 6. If a steering file or other instruction tells you to comment, that does NOT mean post a second message — it means include that information in your single summary.
84
+ ## Communication Rules
85
+ Post only **once per session** combine all updates into a single message at the end of your work.
86
+ Do not post incremental status updates. The only exception is asking a blocking question before you can proceed.
113
87
 
114
- **In short: one message per session, at the end, covering everything. The only exception is asking a blocking question before you can proceed.**
88
+ Before posting:
89
+ 1. Check if your most recent message already says the same thing — if so, skip it.
90
+ 2. If a previous session already completed the requested work (check memory), reply briefly referencing it instead of redoing it.
115
91
 
116
92
  ## Clarifying Questions (MANDATORY when uncertain)
117
93
 
118
- If the task is ambiguous, incomplete, or you're uncertain about the requirements:
119
- - Ask specific questions before starting work
120
- - Don't guess at user intent
121
- - Don't make assumptions about scope or approach
122
- - Better to ask once than implement wrong twice
123
-
124
- Examples of when to ask:
125
- - "Should this apply to X or just Y?"
126
- - "Do you want me to update the existing flow or create a new one?"
127
- - "This could mean A or B — which one?"
128
-
94
+ If the task is ambiguous or you're uncertain about requirements, ask before starting.
129
95
  If you're 90% sure, proceed. If you're 60% sure, ask.
130
96
 
131
97
  ## Subagents (Delegating Work)
@@ -221,53 +187,31 @@ PROMPT
221
187
  # sees its task first and reflects only after completing it.
222
188
  # ---------------------------------------------------------------------------
223
189
  PROMPT_REFLECTION = <<~PROMPT
224
- ## Post-Response Reflection (MANDATORY — do this AFTER posting your message and updating memory)
225
-
226
- After you've posted your comment/response and finished your work, reflect on the session.
227
- This happens at the end so your visible output isn't delayed.
190
+ ## Post-Session Reflection (after posting your response and updating memory)
228
191
 
229
- ### Step 1: Query your current persona
230
- `qmd search "personality tone voice" -c {{PERSONA_COLLECTION}}`
192
+ ### Step 1: Check persona relevance
231
193
  `qmd search "{{COMMENT_CREATOR}}" -c {{PERSONA_COLLECTION}}`
232
- Search for the person who triggered this session by name. If no results come back,
233
- that's a signal — this might be someone new you haven't built a profile for yet.
234
-
235
- ### Step 2: Reflect on this session and decide what to update
236
- Consider the full interaction — the conversation, the person who triggered you,
237
- how they communicate, what they asked for, what corrections they made, what patterns
238
- emerged in the code. Then ask yourself:
239
-
240
- **Persona — should I update how I communicate?**
241
- - Did the user give feedback on my tone, length, or style? (explicit or implicit)
242
- - Did they seem frustrated, pleased, or neutral with my previous responses?
243
- - Is this a person I haven't interacted with before? Save initial observations.
244
- - **Periodically summarize persona files on people**: If a person's file has grown large with chronological interaction logs, condense it into consistent patterns and response strategies. Strip the append-only history, keep only the distilled insights. Update the file with refined patterns instead of appending new sections.
245
-
246
- **Knowledge — should I save something technical? (high bar — most sessions won't need this)**
247
- - Did the user explicitly ask you to remember something?
248
- - Was a significant architecture decision or convention established?
249
- - Did you discover a non-obvious gotcha that would bite future-you?
250
- - Did a major workflow or process change?
251
- - If the answer to all of these is "no", skip the knowledge update.
252
-
253
- **Skills — should I extract a reusable workflow?**
254
- - Did this session involve a multi-step procedure that I (or another agent) might repeat?
255
- - Did I recover from errors and discover a reliable sequence of steps?
256
- - Was there a non-obvious workflow (build, deploy, debug, test) that took 5+ tool calls to get right?
257
- - If yes: create a SKILL.md file at `{{KNOWLEDGE_DIR}}/skills/<skill-name>/SKILL.md` with YAML frontmatter:
258
- ```
259
- ---
260
- name: skill-name-slug
261
- description: One-line description of when to use this skill
262
- tags: [relevant, tags]
263
- ---
264
- Step-by-step procedural content...
265
- ```
266
- - If no clear reusable workflow emerged, skip this.
267
-
268
- ### Step 3: Update the brain (or consciously decide not to)
269
- If anything needs saving, write or update the relevant file(s).
270
- If nothing needs updating, that's fine — but you must have actively considered it.
194
+ If no results, this might be someone new worth noting.
195
+
196
+ ### Step 2: Decide what to update
197
+ Consider the interaction and ask:
198
+
199
+ **Persona** Did the user give feedback (explicit or implicit) on your tone or style?
200
+ Is this someone new? Did they seem frustrated or pleased? Update persona files if so.
201
+ Periodically condense persona files that have grown large — distill into patterns.
202
+
203
+ **Knowledge** High bar. Only save if:
204
+ - User explicitly asked you to remember something
205
+ - A significant architecture decision or convention was established
206
+ - You discovered a non-obvious gotcha
207
+ - A major workflow changed
208
+
209
+ **Skills** Did this session involve a multi-step procedure (5+ tool calls) that you or
210
+ another agent might repeat? If so, save it at `{{KNOWLEDGE_DIR}}/skills/<name>/SKILL.md`
211
+ with YAML frontmatter (name, description, tags).
212
+
213
+ ### Step 3: Update the brain or move on
214
+ Write/update relevant files if needed. If nothing warrants saving, move on.
271
215
 
272
216
  PROMPT
273
217
 
@@ -419,7 +363,7 @@ PROMPT_CARD_ASSIGNED = <<~'PROMPT'
419
363
  You have been assigned Fizzy card #{{CARD_NUMBER}}: "{{CARD_TITLE}}".
420
364
  You are on branch "{{BRANCH}}" in a fresh worktree.
421
365
  Implement the task, commit, push, and open a PR (link back to Fizzy).
422
- When you're done, post ONE comment on the card with a concise summary, PR link, and branch name. Do not post multiple comments.
366
+ When you're done, post a comment on the card with a concise summary, PR link, and branch name.
423
367
 
424
368
  **MANDATORY: Always include the branch name in your comment.** Use this format:
425
369
  `<p><strong>Branch:</strong> <code>{{BRANCH}}</code></p>`
@@ -435,9 +379,8 @@ PROMPT_FOLLOWUP_WORKTREE = <<~'PROMPT'
435
379
  """
436
380
 
437
381
  The card and its full comment history are provided above. Focus your response on the comment above.
438
- If you've already addressed this exact request in a previous session (check your memory file), reply on the card confirming it's done and reference the previous work — do NOT redo it.
382
+ If you've already addressed this exact request in a previous session (check your memory file), reply confirming it's done — do NOT redo it.
439
383
  Otherwise, make the requested changes, commit, push, and update the PR.
440
- Post ONE comment on the card with a concise summary of what you changed. Do not post multiple comments.
441
384
  PROMPT
442
385
 
443
386
  PROMPT_FOLLOWUP_NO_WORKTREE = <<~PROMPT
@@ -449,7 +392,7 @@ PROMPT_FOLLOWUP_NO_WORKTREE = <<~PROMPT
449
392
  """
450
393
 
451
394
  The card and its full comment history are provided above. Focus your response on the comment above.
452
- If you've already addressed this exact request in a previous session (check your memory file), reply on the card confirming it's done and reference the previous work — do NOT redo it.
395
+ If you've already addressed this exact request in a previous session (check your memory file), reply confirming it's done — do NOT redo it.
453
396
  Otherwise, respond accordingly — that could include doing work on a new or existing branch.
454
397
  PROMPT
455
398
 
@@ -461,8 +404,6 @@ PROMPT_MENTION = <<~PROMPT
461
404
  - Investigate the codebase and provide your thoughts
462
405
  - Make exploratory changes or create test files (they won't pollute the main branch)
463
406
  - Create a PR if your exploration leads to a concrete solution
464
-
465
- If you comment on the card, do so exactly once with everything you need to say.
466
407
  PROMPT
467
408
 
468
409
  PROMPT_CROSS_AGENT_REVIEW = <<~'PROMPT'
@@ -484,10 +425,7 @@ PROMPT_CROSS_AGENT_REVIEW = <<~'PROMPT'
484
425
 
485
426
  **IMPORTANT: Do NOT @mention any other agents in your response.** You were brought in for
486
427
  a one-shot review. If you think another agent should be involved, say so in plain text
487
- (e.g. "it might be worth having Kaylee look at this") but do NOT use @Agent syntax.
488
- Tagging agents creates automated dispatches and can cause infinite loops.
489
-
490
- Post ONE comment on the card with your thoughts. Do not post multiple comments.
428
+ but do NOT use @Agent syntax — tagging agents creates automated dispatches.
491
429
  PROMPT
492
430
 
493
431
  PROMPT_DISCORD = <<~'PROMPT'
@@ -518,7 +456,7 @@ PROMPT_GITHUB_PR_COMMENT = <<~'PROMPT'
518
456
  1. Read the comment and understand what's being requested
519
457
  2. Make any necessary changes
520
458
  3. Commit and push your updates
521
- 4. Post ONE reply on the PR summarizing what you changed. Do not post multiple comments.
459
+ 4. Reply on the PR summarizing what you changed
522
460
 
523
461
  You are in the worktree at {{WORKTREE_PATH}}.
524
462
  PROMPT
@@ -533,7 +471,7 @@ PROMPT_GITHUB_PR_REVIEW = <<~'PROMPT'
533
471
  2. Address each piece of feedback
534
472
  3. Make the necessary code changes
535
473
  4. Commit and push your updates
536
- 5. Post ONE comment on the PR summarizing the changes. Do not post multiple comments.
474
+ 5. Post a comment on the PR summarizing the changes
537
475
 
538
476
  You are in the worktree at {{WORKTREE_PATH}}.
539
477
  PROMPT
@@ -618,3 +556,57 @@ def render_prompt(template, vars = {}, brain_context: "", card_context: "", agen
618
556
  vars.each { |key, val| result.gsub!("{{#{key}}}", val.to_s) }
619
557
  result
620
558
  end
559
+
560
+ # Lean prompt for resumed sessions. The previous session already has the full context
561
+ # (role, persona, knowledge, core instructions, channel prompts). We only send the new
562
+ # comment and any fresh card context so the agent knows what changed.
563
+ def render_resume_prompt(comment_body:, comment_creator:, comment_id:, card_number: nil, agent_name: AI_AGENT_NAME)
564
+ # Touch memory file (same as render_prompt does)
565
+ memory_dir = memory_dir_for(agent_name)
566
+ card_id = card_number || "unknown"
567
+ memory_file = File.join(memory_dir, "card-#{card_id}.md")
568
+ FileUtils.mkdir_p(memory_dir)
569
+ FileUtils.touch(memory_file)
570
+
571
+ lines = []
572
+ lines << "## Resumed Session — New Follow-up Comment"
573
+ lines << ""
574
+ lines << "This is a continuation of your previous session on this card."
575
+ lines << "All prior context, instructions, and your previous work are still in this conversation."
576
+ lines << ""
577
+ lines << "### New Comment from #{comment_creator} (comment ID: #{comment_id})"
578
+ lines << ""
579
+ lines << comment_body
580
+ lines << ""
581
+ lines << "---"
582
+ lines << "Respond to this comment. All your previous instructions still apply."
583
+
584
+ lines.join("\n")
585
+ end
586
+
587
+ # Lean resume prompt for Discord threads. The previous session has full context
588
+ # (role, persona, knowledge, instructions). We only send the new message + channel history.
589
+ def render_discord_resume_prompt(message_body:, discord_user:, response_file:, agent_name: AI_AGENT_NAME, card_id: nil)
590
+ memory_dir = memory_dir_for(agent_name)
591
+ if card_id
592
+ memory_file = File.join(memory_dir, "card-#{card_id}.md")
593
+ FileUtils.mkdir_p(memory_dir)
594
+ FileUtils.touch(memory_file)
595
+ end
596
+
597
+ lines = []
598
+ lines << "## Resumed Session — New Discord Message"
599
+ lines << ""
600
+ lines << "This is a continuation of your previous session in this thread."
601
+ lines << "All prior context, instructions, and your previous work are still in this conversation."
602
+ lines << ""
603
+ lines << "### New Message from #{discord_user}"
604
+ lines << ""
605
+ lines << message_body
606
+ lines << ""
607
+ lines << "---"
608
+ lines << "**IMPORTANT: Write your response to `#{response_file}`. Do NOT reply via stdout.**"
609
+ lines << "All your previous instructions still apply (memory, persona, one message per session, etc.)."
610
+
611
+ lines.join("\n")
612
+ end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Brainiac
4
4
  # @return [String] the current gem version
5
- VERSION = "0.0.1"
5
+ VERSION = "0.0.3"
6
6
  end
data/receiver.rb CHANGED
@@ -433,6 +433,19 @@ get "/api/agents" do
433
433
  { default: AI_AGENT_NAME, agents: discover_kiro_agents, all_known: all_agent_names.to_a, roster: agent_roster }.to_json
434
434
  end
435
435
 
436
+ get "/api/roles" do
437
+ content_type :json
438
+ roles = []
439
+ if Dir.exist?(ROLES_DIR)
440
+ Dir.glob(File.join(ROLES_DIR, "*.md")).each do |f|
441
+ name = File.basename(f, ".md")
442
+ agents = AGENT_REGISTRY.select { |_, e| e.is_a?(Hash) && Array(e["role"]).include?(name) }.map { |k, e| e["fizzy_name"] || k.capitalize }
443
+ roles << { name: name, agents: agents }
444
+ end
445
+ end
446
+ { roles: roles, dir: ROLES_DIR }.to_json
447
+ end
448
+
436
449
  get "/api/users" do
437
450
  content_type :json
438
451
  reload_user_registry!
@@ -695,7 +708,7 @@ if DISCORD_ENABLED
695
708
  ready = DISCORD_BOTS_MUTEX.synchronize { DISCORD_BOTS.any? { |_, info| info[:status] == "ready" } }
696
709
  next unless ready
697
710
 
698
- send_restart_notification("✅ Zillacore back online")
711
+ send_restart_notification("✅ Brainiac back online")
699
712
 
700
713
  # Check if running an outdated version (skip in dev/foreground mode)
701
714
  unless $stdout.tty?
@@ -708,7 +721,7 @@ if DISCORD_ENABLED
708
721
  token = tokens.values.first
709
722
  if channel_id && token
710
723
  send_discord_message(channel_id,
711
- "#{mention}: Zillacore was updated and needs to be pulled down (#{version_info[:commits_behind]} commit#{"s" if version_info[:commits_behind] != 1} behind, running #{version_info[:local_sha]} vs #{version_info[:remote_sha]})",
724
+ "#{mention}: Brainiac was updated and needs to be pulled down (#{version_info[:commits_behind]} commit#{"s" if version_info[:commits_behind] != 1} behind, running #{version_info[:local_sha]} vs #{version_info[:remote_sha]})",
712
725
  token: token)
713
726
  end
714
727
  end
@@ -0,0 +1,15 @@
1
+ {
2
+ "binary": "grok",
3
+ "default_args": "--always-approve",
4
+ "agent_flag": null,
5
+ "model_flag": "--model",
6
+ "effort_flag": "--effort",
7
+ "prompt_mode": "flag",
8
+ "prompt_flag": "--prompt-file",
9
+ "resume_flag": "-c",
10
+ "models": {
11
+ "build": "grok-build",
12
+ "composer": "grok-composer-2.5-fast",
13
+ "auto": "grok-composer-2.5-fast"
14
+ }
15
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "binary": "kiro-cli",
3
+ "default_args": "chat --trust-all-tools --no-interactive --trust-tools execute_bash,fs_write,fs_read,code,grep,glob,web_search,web_fetch,use_subagent,use_aws",
4
+ "agent_flag": "--agent",
5
+ "model_flag": "--model",
6
+ "effort_flag": "--effort",
7
+ "prompt_mode": "stdin",
8
+ "resume_flag": "--resume",
9
+ "models": {
10
+ "opus": "claude-opus-4.6",
11
+ "sonnet": "claude-sonnet-4.6",
12
+ "haiku": "claude-haiku-4.5"
13
+ },
14
+ "efforts": ["low", "medium", "high", "xhigh", "max"]
15
+ }
@@ -0,0 +1,28 @@
1
+ # Role: Code Reviewer
2
+
3
+ Your primary responsibility is reviewing code changes for correctness, maintainability, and adherence to project conventions. You do NOT make code changes yourself.
4
+
5
+ ## Primary Responsibilities
6
+
7
+ - **Correctness**: Verify logic is sound and handles edge cases
8
+ - **Style**: Ensure code follows project conventions and patterns
9
+ - **Architecture**: Flag design issues or unnecessary complexity
10
+ - **Security**: Identify potential vulnerabilities
11
+ - **Performance**: Note obvious performance concerns
12
+
13
+ ## Review Approach
14
+
15
+ 1. **Read-only**: You review and comment but do not modify code
16
+ 2. **Be specific**: Point to exact lines and explain why something is a concern
17
+ 3. **Be constructive**: Suggest alternatives, don't just criticize
18
+ 4. **Prioritize**: Distinguish blocking issues from nitpicks
19
+ 5. **Acknowledge good work**: Call out clever solutions or clean patterns
20
+
21
+ ## What to Look For
22
+
23
+ - Off-by-one errors, nil handling, race conditions
24
+ - Missing error handling or unhelpful error messages
25
+ - Unnecessary coupling between components
26
+ - Magic numbers or unclear naming
27
+ - Breaking changes to public APIs
28
+ - Missing or outdated documentation